Чи може 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);
    }
}

2
Дякую! Це допомогло мені уникнути OutOfMemoryException, який я отримував, коли я серіалізував дуже велику колекцію об'єктів у рядок, а потім записував цей рядок у свій потік (замість того, щоб просто серіалізувати безпосередньо до потоку).
Джон Шнайдер

2
Чому змити? Хіба виклик розпорядження, викликаний блоком використання, вже не робить цього?
Şafak Gür

як ним користуватися?
Сана

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

Сподіваюся, це допоможе комусь іншому.


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

Я прийшов до цього питання, шукаючи спосіб передати відкритий список об'єктів на a 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 це можна зробити в 3 рядках коду.
Нік Сотірос
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.