O Json.NET pode serializar / desserializar para / de um fluxo?

151

Ouvi dizer que o Json.NET é mais rápido que o DataContractJsonSerializer e queria experimentá-lo ...

Mas não consegui encontrar nenhum método no JsonConvert que aceite um fluxo em vez de uma string.

Para desserializar um arquivo que contém JSON no WinPhone, por exemplo, eu uso o código a seguir para ler o conteúdo do arquivo em uma sequência e depois desserializar para JSON. Parece ser cerca de 4 vezes mais lento nos meus testes (muito ad-hoc) do que usar o DataContractJsonSerializer para desserializar diretamente do fluxo ...

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

Estou fazendo errado?

Omri Gazitt
fonte

Respostas:

58

ATUALIZAÇÃO: isso não funciona mais na versão atual, veja abaixo a resposta correta ( não há necessidade de voto negativo, isso está correto nas versões mais antigas ).

Use a JsonTextReaderclasse com a StreamReaderou use a JsonSerializersobrecarga que leva StreamReaderdiretamente:

var serializer = new JsonSerializer();
serializer.Deserialize(streamReader);
Paul Tyng
fonte
23
Certeza de que isso não funciona mais. Você tem que usar um JsonReader ou TextReader
BradLaney
8
Você pode incluir o número da versão em que ainda está trabalhando, para que as pessoas saibam quando rolar para baixo.
PoeHaH 24/10
@BradLaney yup JsonTextReader (givenStreamReader) é a maneira de ir agora
Antoine Meltzheim
Obrigado por tomar o tempo para editar a sua resposta re é status e recomendação resposta trabalhando
Nick Touro
281

A versão atual do Json.net não permite que você use o código de resposta aceito. Uma alternativa atual é:

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

Documentação: Desserialize JSON de um fluxo de arquivos

James Newton-King
fonte
4
O JsonTextReader fechará o StreamReader por padrão; portanto, este exemplo pode ser simplificado um pouco, construindo o StreamReader na chamada para o construtor JsonTextReader.
Oliver Bock
1
Alguma idéia de como posso usar um conversor personalizado junto com esse código? Não vejo nenhuma maneira de especificar um conversor para ser usado pelo serializador
alwayslearning
1
Na verdade, tenho uma exceção OutOfMemory e já uso esse código, exatamente. O que, acredito, vale dizer, isso não é uma garantia - se o objeto desserializado for grande o suficiente e você estiver preso em um processo de 32 bits, ainda poderá receber erros de memória com este código
PandaWood
1
Estou recebendo um erro "Não foi possível encontrar o tipo ou nome do namespace 'JsonTextReader'" ... alguma sugestão?
Hnvasa
1
Eu precisava adicionar stream.Position = 0;para desserializar corretamente meu 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);
    }
}
ygaradon
fonte
2
Obrigado! Isso me ajudou a evitar uma OutOfMemoryException que eu estava recebendo quando serializava uma coleção de objetos muito grande em uma string e, em seguida, escrevia essa string no meu fluxo (em vez de apenas serializar diretamente no fluxo).
Jon Schneider
2
Por que liberar? A chamada Dispose, causada pelo bloco using, já não faz isso?
17afak Gür
como usá-lo ?
Sana
2
Nota lateral, pois pode ajudar outras pessoas: se você usar, JsonSerializer ser = JsonSerializer.Create(settings);poderá definir quais configurações usar durante a des serialização.
Mike
1
Um possível problema com essa Serializeimplementação é que ela fecha o Streampassado como argumento, o que, dependendo do aplicativo, pode ser um problema. Com o .NET 4.5+, você pode evitar esse problema usando uma StreamWritersobrecarga de construtor com um parâmetro leaveOpenque permite deixar o fluxo aberto.
Joe
29

Eu escrevi uma classe de extensão para me ajudar a desserializar de fontes JSON (string, stream, arquivo).

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

Desserializar agora é tão fácil quanto escrever:

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

Espero que ajude alguém.

Tok '
fonte
2
Contra : poluirá todas as strings com os métodos de extensão. Soluções alternativas : Apenas declarar Using SomeJsonHelpersNamespacequando necessário ou remover a thispalavra-chave e usar JsonHelpers.CreateFromJsonString(someJsonString) Pro : é tão fácil de usar :)
Tok'
1
Embora possa ser visto como "poluente", quase metade das extensões no objeto String pode ser vista da mesma maneira. Isso estende um objeto de uma maneira vista como útil para qualquer pessoa que consistentemente alterasse de string (json) para JSON.
vipersassassin
Também o uso Encoding.Defaulté ruim, pois se comportará de maneira diferente em máquinas diferentes (consulte o grande aviso no documento da Microsoft). O JSON deve ser UTF-8 e é isso que o JsonSerializer espera. Assim deveria ser Encoding.UTF8. O código como está produzirá cadeias ilegíveis ou falhará na desserialização se forem usados ​​caracteres não ASCII.
ckuri
17

Cheguei a essa pergunta procurando uma maneira de transmitir uma lista de objetos abertos para um System.IO.Streame lê-los do outro lado, sem armazenar a lista inteira antes de enviá-los. (Especificamente, estou transmitindo objetos persistentes do MongoDB pela API da Web.)

@Paul Tyng e @Rivers fizeram um excelente trabalho respondendo à pergunta original, e eu usei as respostas deles para criar uma prova de conceito para o meu problema. Decidi postar meu aplicativo de console de teste aqui, caso mais alguém esteja enfrentando o mesmo 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);
            }
        }
    }
}

Observe que você pode receber uma exceção quando o AnonymousPipeServerStreamitem for descartado. Eu ignorei isso, pois não é relevante para o problema em questão.

Blake Mitchell
fonte
1
Eu preciso modificar isso para que eu possa obter qualquer objeto JSON completo. Meu servidor e cliente se comunicam enviando trechos de JSON para que o cliente possa enviar {"sign in":{"username":"nick"}}{"buy item":{"_id":"32321123"}}e precisa ver isso como dois fragmentos de JSON sinalizando um evento cada vez que ele lê um fragmento. No nodejs, isso pode ser feito em 3 linhas de código.
Nick Sotiros