¿Puede Json.NET serializar / deserializar hacia / desde una transmisión?

151

He oído que Json.NET es más rápido que DataContractJsonSerializer, y quería probarlo ...

Pero no pude encontrar ningún método en JsonConvert que tome una secuencia en lugar de una cadena.

Para deserializar un archivo que contiene JSON en WinPhone, por ejemplo, utilizo el siguiente código para leer el contenido del archivo en una cadena y luego deserializarlo en JSON. Parece ser aproximadamente 4 veces más lento en mis pruebas (muy ad-hoc) que usar DataContractJsonSerializer para deserializar directamente de la transmisión ...

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

¿Lo estoy haciendo mal?

Omri Gazitt
fuente

Respuestas:

58

ACTUALIZACIÓN: Esto ya no funciona en la versión actual, consulte a continuación la respuesta correcta ( no es necesario votar, esto es correcto en las versiones anteriores ).

Use la JsonTextReaderclase con a StreamReadero use la JsonSerializersobrecarga que toma un StreamReaderdirectamente:

var serializer = new JsonSerializer();
serializer.Deserialize(streamReader);
Paul Tyng
fuente
23
Estoy bastante seguro de que esto ya no funciona. Tienes que usar un JsonReader o TextReader
BradLaney
8
Es posible que desee incluir el número de versión en el que todavía está trabajando para que las personas sepan cuándo desplazarse hacia abajo.
PoeHaH
@BradLaney yup JsonTextReader (givenStreamReader) es el camino a seguir ahora
Antoine Meltzheim
Gracias por tomarse el tiempo para editar su respuesta con respecto al estado de funcionamiento y la recomendación de respuesta
Nick Bull
281

La versión actual de Json.net no le permite usar el código de respuesta aceptado. Una alternativa actual es:

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

Documentación: Deserializar JSON de una secuencia de archivos

James Newton-King
fuente
44
JsonTextReader cerrará su StreamReader de forma predeterminada, por lo que este ejemplo podría simplificarse un poco construyendo StreamReader en la llamada al constructor JsonTextReader.
Oliver Bock
1
¿Alguna idea de cómo puedo usar un convertidor personalizado junto con este código? No hay forma de especificar un convertidor para ser utilizado por el serializador
aprendiendo el
1
En realidad, tengo una excepción OutOfMemory y ya uso este código, casi exactamente. Lo que, creo, va a decir, esto no es una garantía: si el objeto deserializado es lo suficientemente grande y está atrapado en un proceso de 32 bits, aún puede obtener errores de memoria con este código
PandaWood
1
recibo un error "No se pudo encontrar el tipo o el nombre del espacio de nombres 'JsonTextReader'" ... ¿alguna sugerencia?
hnvasa
1
Necesitaba agregar stream.Position = 0;para deserializar correctamente mi 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
fuente
2
¡Gracias! Esto me ayudó a evitar una OutOfMemoryException que estaba recibiendo cuando estaba serializando una colección de objetos muy grande en una cadena, y luego escribiendo esa cadena en mi secuencia (en lugar de simplemente serializar directamente a la secuencia).
Jon Schneider
2
¿Por qué enjuagar? ¿La llamada Dispose causada por el bloque using ya no hace eso?
Şafak Gür
cómo usarlo ?
Sana
2
Nota al margen, porque podría ayudar a otros: si lo usa JsonSerializer ser = JsonSerializer.Create(settings);, puede definir qué configuraciones usar durante la des / serialización.
Mike
1
Un problema potencial con esta Serializeimplementación es que cierra el Streampaso como argumento, lo que dependiendo de la aplicación puede ser un problema. Con .NET 4.5+ puede evitar este problema utilizando una StreamWritersobrecarga del constructor con un parámetro leaveOpenque le permita dejar abierta la secuencia.
Joe
29

He escrito una clase de extensión para ayudarme a deserializar desde fuentes JSON (cadena, secuencia, archivo).

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

Deserializar ahora es tan fácil como escribir:

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

Espero que ayude a alguien más.

Tok '
fuente
2
En contra : contaminará todas las cadenas con los métodos de extensión. Soluciones alternativas : solo declare Using SomeJsonHelpersNamespacedonde sea necesario o elimine la thispalabra clave y use JsonHelpers.CreateFromJsonString(someJsonString) Pro : es tan fácil de usar :)
Tok
1
Aunque podría verse como "contaminante", casi la mitad de las extensiones en el objeto String podrían verse de la misma manera. Esto extiende un objeto de una manera que se considera útil para cualquier persona que cambie constantemente de cadena (json) a JSON.
vipersassassin
El uso también Encoding.Defaultes malo, ya que se comportará de manera diferente en diferentes máquinas (consulte la gran advertencia en el documento de Microsoft). Se espera que JSON sea UTF-8 y esto es lo que JsonSerializer espera. Así debería ser Encoding.UTF8. El código tal cual producirá cadenas ilegibles o no se deserializará si se utilizan caracteres no ASCII.
ckuri
17

Llegué a esta pregunta en busca de una forma de transmitir una lista abierta de objetos en un System.IO.Streamy leerlos desde el otro extremo, sin almacenar la lista completa antes de enviarlos. (Específicamente, estoy transmitiendo objetos persistentes de MongoDB a través de la API web).

@Paul Tyng y @Rivers hicieron un excelente trabajo respondiendo la pregunta original, y usé sus respuestas para crear una prueba de concepto para mi problema. Decidí publicar mi aplicación de consola de prueba aquí en caso de que alguien más esté enfrentando el mismo 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);
            }
        }
    }
}

Tenga en cuenta que puede recibir una excepción cuando AnonymousPipeServerStreamse elimine, lo ignoré ya que no es relevante para el problema en cuestión.

Blake Mitchell
fuente
1
Necesito modificar esto para poder obtener cualquier objeto JSON completo. Mi servidor y mi cliente se comunican enviando fragmentos de JSON para que el cliente pueda enviar {"sign in":{"username":"nick"}}{"buy item":{"_id":"32321123"}}y necesita ver esto como dos fragmentos de JSON que señalan un evento cada vez que lee un fragmento. En nodejs esto se puede hacer en 3 líneas de código.
Nick Sotiros el