Может ли Json.NET сериализовать / десериализовать в / из потока?

151

Я слышал, что Json.NET быстрее, чем DataContractJsonSerializer, и хотел попробовать ...

Но я не смог найти какие-либо методы на JsonConvert, которые принимают поток, а не строку.

Например, для десериализации файла, содержащего JSON на WinPhone, я использую следующий код, чтобы прочитать содержимое файла в строку, а затем десериализовать в JSON. В моем (очень специальном) тестировании это примерно в 4 раза медленнее, чем при использовании DataContractJsonSerializer для десериализации прямо из потока ...

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

Я делаю это неправильно?

Омри Газитт
источник

Ответы:

58

ОБНОВЛЕНИЕ: Это больше не работает в текущей версии, правильный ответ см. Ниже ( нет необходимости голосовать, это верно для более старых версий ).

Используйте JsonTextReaderкласс с StreamReaderили используйте JsonSerializerперегрузку, которая принимает StreamReaderнепосредственно:

var serializer = new JsonSerializer();
serializer.Deserialize(streamReader);
Пол Тынг
источник
23
Уверен, это больше не работает. Вы должны использовать JsonReader или TextReader
BradLaney
8
Возможно, вы захотите указать номер версии, над которой все еще работает, чтобы люди знали, когда прокручивать страницу вниз.
PoeHaH
@BradLaney yup JsonTextReader (GivenStreamReader) - это путь, по
которому
Спасибо, что
Nick Bull
281

Текущая версия Json.net не позволяет использовать принятый код ответа. Текущая альтернатива:

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

Документация: десериализация JSON из файлового потока

Джеймс Ньютон-Кинг
источник
4
JsonTextReader закроет свой StreamReader по умолчанию, поэтому этот пример можно немного упростить, создав StreamReader при вызове конструктора JsonTextReader.
Оливер Бок
1
Любая идея, как я могу использовать пользовательский конвертер вместе с этим кодом? Не вижу способа указать конвертер, который будет использовать сериализатор
всегда учиться
1
На самом деле, у меня есть исключение OutOfMemory, и я уже использую этот код, в значительной степени точно. Что, я полагаю, само собой разумеется, не является гарантией - если десериализованный объект достаточно велик и вы застряли в 32-битном процессе, вы все равно можете получить ошибки памяти с этим кодом
PandaWood
1
я получаю сообщение об ошибке «Не удалось найти тип или имя пространства имен« JsonTextReader »... какие-либо предложения?
hnvasa
1
Мне нужно было добавить, stream.Position = 0;чтобы правильно десериализовать мой 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
источник
2
Спасибо! Это помогло мне избежать исключения OutOfMemoryException, которое я получал, когда сериализовал очень большую коллекцию объектов в строку, а затем записывал эту строку в мой поток (вместо простой сериализации непосредственно в поток).
Джон Шнайдер
2
Почему флеш? Разве вызов Dispose, вызванный блоком using, уже не делает этого?
Шафак
как это использовать ?
Сана
2
Примечание, потому что это может помочь другим: если вы используете, JsonSerializer ser = JsonSerializer.Create(settings);вы можете определить, какие настройки использовать при де / сериализации.
микрофон
1
Одна потенциальная проблема с этой Serializeреализацией заключается в том, что она закрывает Streamпереданный в качестве аргумента, что может быть проблемой в зависимости от приложения. В .NET 4.5+ вы можете избежать этой проблемы, используя StreamWriterперегрузку конструктора с параметром, leaveOpenкоторый позволяет оставить поток открытым.
Джо
29

Я написал класс расширения, чтобы помочь мне десериализовать из источников JSON (строка, поток, файл).

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

Десериализацию теперь так же просто, как написать:

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

Надеюсь, это поможет кому-то еще.

Tok»
источник
2
Против : он будет загрязнять все строки методами расширения. Обходные пути : Объявляйте только Using SomeJsonHelpersNamespaceпри необходимости или удаляйте thisключевое слово и используйте JsonHelpers.CreateFromJsonString(someJsonString) Pro : так проще в использовании :)
Tok '
1
Хотя это можно рассматривать как «загрязняющий», почти половина расширений в объекте String может рассматриваться одинаково. Это расширяет объект способом, который рассматривается как полезный для всех, кто последовательно меняется с строки (json) на JSON.
vipersassassin
Также использование Encoding.Defaultплохо, так как на разных машинах он будет вести себя по-разному (см. Большое предупреждение в документе Microsoft). Ожидается, что JSON будет UTF-8, и именно этого ожидает JsonSerializer. Так и должно быть Encoding.UTF8. Код как есть будет создавать искаженные строки или не сможет десериализоваться, если используются символы не ASCII.
ckuri
17

Я пришел к этому вопросу, ища способ для потоковой передачи открытого списка объектов на System.IO.Stream и считывания их с другого конца, без буферизации всего списка перед отправкой. (В частности, я передаю постоянные объекты из MongoDB через Web API.)

@Paul Tyng и @Rivers отлично ответили на первоначальный вопрос, и я использовал их ответы, чтобы создать доказательство концепции моей проблемы. Я решил опубликовать свое тестовое консольное приложение здесь на случай, если кто-то еще столкнется с той же проблемой.

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

Обратите внимание, что вы можете получить исключение при AnonymousPipeServerStreamутилизации, я проигнорировал это, поскольку это не имеет отношения к рассматриваемой проблеме.

Блейк Митчелл
источник
1
Мне нужно изменить это так, чтобы я мог получить любой полный объект JSON. Мой сервер и клиент обмениваются данными, отправляя фрагменты JSON, чтобы клиент мог отправить, {"sign in":{"username":"nick"}}{"buy item":{"_id":"32321123"}}и он должен видеть это как два фрагмента JSON, сигнализирующих о событии каждый раз, когда он читает фрагмент. В nodejs это можно сделать в трех строках кода.
Ник Сотирос