Kann Json.NET in einen Stream serialisieren / deserialisieren?

151

Ich habe gehört, dass Json.NET schneller als DataContractJsonSerializer ist, und wollte es versuchen ...

Aber ich konnte auf JsonConvert keine Methoden finden, die einen Stream anstelle eines Strings verwenden.

Zum Deserialisieren einer Datei, die JSON auf WinPhone enthält, verwende ich beispielsweise den folgenden Code, um den Dateiinhalt in eine Zeichenfolge zu lesen und dann in JSON zu deserialisieren. In meinen (sehr ad-hoc) Tests scheint es ungefähr viermal langsamer zu sein als mit DataContractJsonSerializer, um direkt aus dem Stream zu deserialisieren ...

// DCJS
DataContractJsonSerializer dc = new DataContractJsonSerializer(typeof(Constants));
Constants constants = (Constants)dc.ReadObject(stream);

// JSON.NET
string json = new StreamReader(stream).ReadToEnd();
Constants constants = JsonConvert.DeserializeObject<Constants>(json);

Mache ich es falsch

Omri Gazitt
quelle

Antworten:

58

UPDATE: Dies funktioniert in der aktuellen Version nicht mehr. Die richtige Antwort finden Sie weiter unten (Sie müssen nicht abstimmen, dies ist bei älteren Versionen korrekt ).

Verwenden Sie die JsonTextReaderKlasse mit a StreamReaderoder verwenden Sie die JsonSerializerÜberladung, die a StreamReaderdirekt benötigt:

var serializer = new JsonSerializer();
serializer.Deserialize(streamReader);
Paul Tyng
quelle
23
Ich bin mir ziemlich sicher, dass das nicht mehr funktioniert. Sie müssen einen JsonReader oder TextReader
BradLaney
8
Möglicherweise möchten Sie die Versionsnummer angeben, an der noch gearbeitet wird, damit die Benutzer wissen, wann sie nach unten scrollen müssen.
PoeHaH
@BradLaney yup JsonTextReader (GivenStreamReader) ist der richtige Weg
Antoine Meltzheim
Vielen Dank, dass Sie sich die Zeit genommen haben, Ihre Antwort bezüglich des Arbeitsstatus und der Antwortempfehlung zu bearbeiten
Nick Bull
280

In der aktuellen Version von Json.net können Sie den akzeptierten Antwortcode nicht verwenden. Eine aktuelle Alternative ist:

public static object DeserializeFromStream(Stream stream)
{
    var serializer = new JsonSerializer();

    using (var sr = new StreamReader(stream))
    using (var jsonTextReader = new JsonTextReader(sr))
    {
        return serializer.Deserialize(jsonTextReader);
    }
}

Dokumentation: Deserialisieren Sie JSON aus einem Dateistream

James Newton-King
quelle
4
JsonTextReader schließt seinen StreamReader standardmäßig, sodass dieses Beispiel durch Erstellen des StreamReader im Aufruf des JsonTextReader-Konstruktors etwas vereinfacht werden kann.
Oliver Bock
1
Irgendeine Idee, wie ich einen benutzerdefinierten Konverter zusammen mit diesem Code verwenden kann? Es gibt keine Möglichkeit, einen Konverter anzugeben, der vom Serializer verwendet werden soll
lernen
1
Eigentlich habe ich eine OutOfMemory-Ausnahme und ich benutze diesen Code bereits ziemlich genau. Was meiner Meinung nach keine Garantie ist - wenn das deserialisierte Objekt groß genug ist und Sie in einem 32-Bit-Prozess stecken, können mit diesem Code immer noch Speicherfehler auftreten
PandaWood
1
Ich erhalte die Fehlermeldung "Der Typ- oder Namespace-Name 'JsonTextReader' wurde nicht gefunden" ... Vorschläge?
Hnvasa
1
Ich musste hinzufügen, stream.Position = 0;um meinen JSON korrekt zu deserialisieren.
Hybrid2102
76
public static void Serialize(object value, Stream s)
{
    using (StreamWriter writer = new StreamWriter(s))
    using (JsonTextWriter jsonWriter = new JsonTextWriter(writer))
    {
        JsonSerializer ser = new JsonSerializer();
        ser.Serialize(jsonWriter, value);
        jsonWriter.Flush();
    }
}

public static T Deserialize<T>(Stream s)
{
    using (StreamReader reader = new StreamReader(s))
    using (JsonTextReader jsonReader = new JsonTextReader(reader))
    {
        JsonSerializer ser = new JsonSerializer();
        return ser.Deserialize<T>(jsonReader);
    }
}
Ygaradon
quelle
2
Vielen Dank! Dies half mir, eine OutOfMemoryException zu vermeiden, die ich bekam, als ich eine sehr große Objektsammlung in eine Zeichenfolge serialisierte und diese Zeichenfolge dann in meinen Stream schrieb (anstatt nur direkt in den Stream zu serialisieren).
Jon Schneider
2
Warum spülen? Tut der Dispose-Aufruf, der durch den using-Block verursacht wird, das nicht bereits?
Şafak Gür
wie man es benutzt?
Sana
2
Randnotiz, weil es anderen helfen könnte: Wenn Sie verwenden JsonSerializer ser = JsonSerializer.Create(settings);, können Sie definieren, welche Einstellungen während der De- / Serialisierung verwendet werden sollen.
Mike
1
Ein mögliches Problem bei dieser SerializeImplementierung besteht darin, dass das Streamübergebene Argument geschlossen wird, was je nach Anwendung ein Problem sein kann. Mit .NET 4.5+ können Sie dieses Problem vermeiden, indem Sie eine StreamWriterKonstruktorüberladung mit einem Parameter verwenden leaveOpen, mit dem Sie den Stream offen lassen können.
Joe
29

Ich habe eine Erweiterungsklasse geschrieben, die mir beim Deserialisieren aus JSON-Quellen (Zeichenfolge, Stream, Datei) hilft.

public static class JsonHelpers
{
    public static T CreateFromJsonStream<T>(this Stream stream)
    {
        JsonSerializer serializer = new JsonSerializer();
        T data;
        using (StreamReader streamReader = new StreamReader(stream))
        {
            data = (T)serializer.Deserialize(streamReader, typeof(T));
        }
        return data;
    }

    public static T CreateFromJsonString<T>(this String json)
    {
        T data;
        using (MemoryStream stream = new MemoryStream(System.Text.Encoding.Default.GetBytes(json)))
        {
            data = CreateFromJsonStream<T>(stream);
        }
        return data;
    }

    public static T CreateFromJsonFile<T>(this String fileName)
    {
        T data;
        using (FileStream fileStream = new FileStream(fileName, FileMode.Open))
        {
            data = CreateFromJsonStream<T>(fileStream);
        }
        return data;
    }
}

Deserialisieren ist jetzt so einfach wie Schreiben:

MyType obj1 = aStream.CreateFromJsonStream<MyType>();
MyType obj2 = "{\"key\":\"value\"}".CreateFromJsonString<MyType>();
MyType obj3 = "data.json".CreateFromJsonFile<MyType>();

Hoffe, es wird jemand anderem helfen.

Tok '
quelle
2
Gegen : Es werden alle Zeichenfolgen mit den Erweiterungsmethoden verschmutzt. Problemumgehungen : Deklarieren Sie nur, Using SomeJsonHelpersNamespacewo dies erforderlich ist, oder entfernen Sie das thisSchlüsselwort und verwenden Sie JsonHelpers.CreateFromJsonString(someJsonString) Pro : Es ist so einfacher zu verwenden :)
Tok
1
Obwohl es als "umweltschädlich" angesehen werden könnte, könnte fast die Hälfte der Erweiterungen im String-Objekt auf die gleiche Weise gesehen werden. Dadurch wird ein Objekt auf eine Weise erweitert, die für jeden nützlich ist, der sich konsistent von Zeichenfolge (json) zu JSON ändert.
Vipersassassin
Auch die Verwendung Encoding.Defaultist schlecht, da sie sich auf verschiedenen Computern unterschiedlich verhält (siehe die große Warnung im Microsoft-Dokument). JSON wird voraussichtlich UTF-8 sein, und dies erwartet JsonSerializer. So sollte es sein Encoding.UTF8. Der Code wie er ist erzeugt verstümmelte Zeichenfolgen oder kann nicht deserialisiert werden, wenn Nicht-ASCII-Zeichen verwendet werden.
Ckuri
17

Ich kam zu dieser Frage und suchte nach einer Möglichkeit, eine offene Liste von Objekten auf eine zu streamen System.IO.Streamund sie am anderen Ende abzulesen, ohne die gesamte Liste vor dem Senden zu puffern. (Insbesondere streame ich persistierte Objekte von MongoDB über die Web-API.)

@Paul Tyng und @Rivers haben die ursprüngliche Frage hervorragend beantwortet, und ich habe ihre Antworten verwendet, um einen Proof of Concept für mein Problem zu erstellen. Ich habe beschlossen, meine Testkonsolen-App hier zu veröffentlichen, falls jemand anderes mit dem gleichen Problem konfrontiert ist.

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace TestJsonStream {
    class Program {
        static void Main(string[] args) {
            using(var writeStream = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.None)) {
                string pipeHandle = writeStream.GetClientHandleAsString();
                var writeTask = Task.Run(() => {
                    using(var sw = new StreamWriter(writeStream))
                    using(var writer = new JsonTextWriter(sw)) {
                        var ser = new JsonSerializer();
                        writer.WriteStartArray();
                        for(int i = 0; i < 25; i++) {
                            ser.Serialize(writer, new DataItem { Item = i });
                            writer.Flush();
                            Thread.Sleep(500);
                        }
                        writer.WriteEnd();
                        writer.Flush();
                    }
                });
                var readTask = Task.Run(() => {
                    var sw = new Stopwatch();
                    sw.Start();
                    using(var readStream = new AnonymousPipeClientStream(pipeHandle))
                    using(var sr = new StreamReader(readStream))
                    using(var reader = new JsonTextReader(sr)) {
                        var ser = new JsonSerializer();
                        if(!reader.Read() || reader.TokenType != JsonToken.StartArray) {
                            throw new Exception("Expected start of array");
                        }
                        while(reader.Read()) {
                            if(reader.TokenType == JsonToken.EndArray) break;
                            var item = ser.Deserialize<DataItem>(reader);
                            Console.WriteLine("[{0}] Received item: {1}", sw.Elapsed, item);
                        }
                    }
                });
                Task.WaitAll(writeTask, readTask);
                writeStream.DisposeLocalCopyOfClientHandle();
            }
        }

        class DataItem {
            public int Item { get; set; }
            public override string ToString() {
                return string.Format("{{ Item = {0} }}", Item);
            }
        }
    }
}

Beachten Sie, dass Sie möglicherweise eine Ausnahme erhalten, wenn das AnonymousPipeServerStreamentsorgt wird. Ich habe dies ignoriert, da es für das vorliegende Problem nicht relevant ist.

Blake Mitchell
quelle
1
Ich muss dies ändern, damit ich jedes vollständige JSON-Objekt erhalten kann. Mein Server und mein Client kommunizieren, indem sie JSON-Snippets senden, damit der Client sie senden {"sign in":{"username":"nick"}}{"buy item":{"_id":"32321123"}}kann. Dies muss als zwei Fragmente von JSON angesehen werden, die jedes Mal ein Ereignis signalisieren, wenn er ein Fragment liest. In nodejs kann dies in 3 Codezeilen erfolgen.
Nick Sotiros