Json.NETはストリームとの間でシリアル化/逆シリアル化できますか?


155

Json.NETはDataContractJsonSerializerよりも高速であると聞いたので、試してみたいと思いました...

しかし、文字列ではなくストリームを受け取るJsonConvertのメソッドは見つかりませんでした。

たとえば、WinPhoneでJSONを含むファイルを逆シリアル化するには、次のコードを使用してファイルの内容を文字列に読み取り、JSONに逆シリアル化します。私の(非常にアドホックな)テストでは、DataContractJsonSerializerを使用してストリームから直接逆シリアル化するよりも約4倍遅いようです...

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

私はそれを間違っていますか?

回答:


57

更新:これは現在のバージョンでは機能しなくなりました。正解については以下を参照してください(投票する必要はありませんこれは古いバージョンでは正しいです)。

JsonTextReaderクラスを使用するStreamReaderJsonSerializerStreamReader直接取るオーバーロードを使用します。

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

25
これが機能しなくなったことは間違いありません。JsonReaderまたはTextReaderを使用する必要があります
BradLaney 2012

9
スクロールダウンするタイミングがわかるように、これがまだ機能しているバージョン番号を含めることをお勧めします。
PoeHaH 2017年

@BradLaneyうんJsonTextReader(givenStreamReader)は今行く方法です
アントワーヌメルツハイム2018

それはステータスと回答勧告働いて再あなたの答えを編集するための時間を割いていただきありがとうございます
ニック・ブル

「投票する必要はありません。これは古いバージョンでは正しいです」-ほとんどの人には関係がなくなったため、より良い答えが存在するため、投票する必要があります。
Ed S.

290

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を閉じるため、この例は、JsonTextReaderコンストラクターの呼び出しでStreamReaderを作成することで少し簡略化できます。
オリバーボック2016年

1
このコードと一緒にカスタムコンバーターを使用する方法はありますか?
シリアライザー

2
実際、OutOfMemory例外があり、すでにこのコードをほぼ正確に使用しています。これは保証ではないと私は信じています-逆シリアル化されたオブジェクトが十分に大きく、32ビットプロセスで立ち往生している場合でも、このコードでメモリエラーが発生する可能性があります
PandaWood 2016年

1
「タイプまたは名前空間名 'JsonTextReader'が見つかりませんでした」というエラーが表示されます...何か提案はありますか?
hnvasa 2017

4
stream.Position = 0;jsonを正しく逆シリアル化するために追加する必要がありました。
hybrid21 0219年

83
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を回避できました(ストリームに直接シリアル化するだけではありません)。
Jon Schneider

3
なぜフラッシュするのですか?usingブロックによって引き起こされたDispose呼び出しは、すでにそれを行っていませんか?
–ŞafakGür 2017

それを使用する方法 ?
サナ

4
補足:他の人に役立つ可能性があるため:使用するJsonSerializer ser = JsonSerializer.Create(settings);場合は、逆シリアル化/シリアル化中に使用する設定を定義できます。
マイク

1
このSerialize実装の潜在的な問題の1つは、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オブジェクトの拡張機能のほぼ半分が同じように表示されます。これにより、string(json)からJSONに一貫して変更されるすべての人にとって有用と見なされる方法でオブジェクトが拡張されます。
vipersassassin 2018

また、使用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の2つのフラグメントとして認識する必要があります。nodejsでは、これは3行のコードで実行できます。
Nick Sotiros 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.