Json.NET può serializzare / deserializzare su / da uno stream?


151

Ho sentito che Json.NET è più veloce di DataContractJsonSerializer e ho voluto provare ...

Ma non sono riuscito a trovare alcun metodo su JsonConvert che utilizzi un flusso anziché una stringa.

Per deserializzare un file contenente JSON su WinPhone, ad esempio, utilizzo il codice seguente per leggere il contenuto del file in una stringa, quindi deserializzare in JSON. Sembra essere circa 4 volte più lento nei miei test (molto ad hoc) rispetto all'utilizzo di DataContractJsonSerializer per deserializzare direttamente dallo stream ...

// 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);

Sto sbagliando?

Risposte:


58

AGGIORNAMENTO: non funziona più nella versione corrente, vedi sotto per la risposta corretta ( non è necessario votare in basso, questo è corretto nelle versioni precedenti ).

Utilizzare la JsonTextReaderclasse con a StreamReadero utilizzare il JsonSerializersovraccarico che accetta StreamReaderdirettamente un :

var serializer = new JsonSerializer();
serializer.Deserialize(streamReader);

23
Abbastanza sicuro che questo non funziona più. Devi usare JsonReader o TextReader
BradLaney il

8
Potresti voler includere il numero di versione su cui funziona ancora in modo che le persone sappiano quando scorrere verso il basso.
PoeHaH,

@BradLaney yup JsonTextReader (givenStreamReader) è la strada da percorrere ora
Antoine Meltzheim,

Grazie per aver dedicato del tempo a modificare la tua risposta in merito allo stato di funzionamento e alla raccomandazione di risposta
Nick Bull

281

La versione corrente di Json.net non consente di utilizzare il codice di risposta accettato. Un'alternativa attuale è:

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);
    }
}

Documentazione: deserializzare JSON da un flusso di file


4
JsonTextReader chiuderà StreamReader per impostazione predefinita, quindi questo esempio potrebbe essere semplificato un po 'costruendo StreamReader nella chiamata al costruttore JsonTextReader.
Oliver Bock,

1
Hai idea di come posso usare un convertitore personalizzato insieme a questo codice? Non vedo alcun modo di specificare un convertitore che deve essere utilizzato dal serializzatore
apprendendo sempre il

1
In realtà, ho un'eccezione OutOfMemory e uso già questo codice, praticamente esattamente. Il che, credo, dice, non è una garanzia - se l'oggetto deserializzato è abbastanza grande e sei bloccato in un processo a 32 bit, potresti ancora avere errori di memoria con questo codice
PandaWood,

1
visualizzo un errore "Impossibile trovare il tipo o il nome dello spazio dei nomi 'JsonTextReader' ..." suggerimenti?
hnvasa,

1
Ho dovuto aggiungere stream.Position = 0;per deserializzare correttamente il mio json.
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);
    }
}

2
Grazie! Questo mi ha aiutato a evitare una OutOfMemoryException che stavo ottenendo mentre stavo serializzando una raccolta di oggetti molto grande su una stringa e quindi scrivendo quella stringa nel mio flusso (invece di limitarmi a serializzare direttamente nel flusso).
Jon Schneider,

2
Perché arrossire? La chiamata Dispose causata dal blocco using non lo fa già?
Şafak Gür

come usarlo ?
Sana,

2
Nota a margine, perché potrebbe aiutare gli altri: se lo usi JsonSerializer ser = JsonSerializer.Create(settings);puoi definire quali impostazioni usare durante la de / serializzazione.
mike

1
Un potenziale problema con questa Serializeimplementazione è che chiude il Streampassato come argomento, che a seconda dell'applicazione può essere un problema. Con .NET 4.5+ è possibile evitare questo problema utilizzando un StreamWritersovraccarico del costruttore con un parametro leaveOpenche consente di lasciare aperto lo stream.
Joe,

29

Ho scritto una classe di estensione per aiutarmi a deserializzare dalle fonti JSON (stringa, flusso, file).

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;
    }
}

Deserializzare ora è facile come scrivere:

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

Spero che possa aiutare qualcun altro.


2
Contro : inquinerà tutte le stringhe con i metodi di estensione. Soluzioni alternative : dichiarare solo Using SomeJsonHelpersNamespacedove necessario o rimuovere la thisparola chiave e utilizzare JsonHelpers.CreateFromJsonString(someJsonString) Pro : è così più facile da usare :)
Tok

1
Sebbene possa essere visto come "inquinante", quasi la metà delle estensioni nell'oggetto String potrebbe essere vista allo stesso modo. Questo estende un oggetto in un modo considerato utile a chiunque cambierebbe costantemente da stringa (json) a JSON.
vipersassassin,

Anche l'utilizzo Encoding.Defaultè negativo in quanto si comporterà in modo diverso su macchine diverse (vedere l'avvertimento grande nel documento Microsoft). JSON dovrebbe essere UTF-8 e questo è ciò che JsonSerializer si aspetta. Così dovrebbe essere Encoding.UTF8. Il codice così com'è produrrà stringhe confuse o non riuscirà a deserializzare se vengono utilizzati caratteri non ASCII.
ckuri,

17

Sono arrivato a questa domanda alla ricerca di un modo per trasmettere in streaming un elenco di oggetti senza fine su un System.IO.Streame leggerli dall'altra parte, senza bufferizzare l'intero elenco prima di inviarlo. (In particolare sto trasmettendo oggetti persistenti da MongoDB su API Web.)

@Paul Tyng e @Rivers hanno fatto un ottimo lavoro rispondendo alla domanda originale, e ho usato le loro risposte per costruire una prova di concetto per il mio problema. Ho deciso di pubblicare qui la mia app della console di test nel caso in cui altri si trovino ad affrontare lo stesso problema.

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);
            }
        }
    }
}

Nota che potresti ricevere un'eccezione quando AnonymousPipeServerStreamviene eliminata, l'ho ignorata poiché non è rilevante per il problema in questione.


1
Devo modificarlo in modo da poter ottenere qualsiasi oggetto JSON completo. Il mio server e client comunicano inviando frammenti di JSON in modo che il client possa inviarlo {"sign in":{"username":"nick"}}{"buy item":{"_id":"32321123"}}e deve vederlo come due frammenti di JSON che segnalano un evento ogni volta che legge un frammento. In nodejs questo può essere fatto in 3 righe di codice.
Nick Sotiros,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.