Bisakah Json.NET membuat serial / deserialize ke / dari stream?

151

Saya telah mendengar bahwa Json.NET lebih cepat daripada DataContractJsonSerializer, dan ingin mencobanya ...

Tapi saya tidak bisa menemukan metode apa pun di JsonConvert yang mengambil aliran daripada string.

Untuk deserialisasi file yang berisi JSON di WinPhone, misalnya, saya menggunakan kode berikut untuk membaca konten file menjadi string, dan kemudian deserialize ke JSON. Tampaknya sekitar 4 kali lebih lambat dalam pengujian (sangat ad-hoc) saya daripada menggunakan DataContractJsonSerializer untuk deserialisasi langsung dari aliran ...

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

Apakah saya salah?

Omri Gazitt
sumber

Jawaban:

58

UPDATE: Ini tidak lagi berfungsi dalam versi saat ini, lihat di bawah untuk jawaban yang benar ( tidak perlu memilih, ini benar pada versi yang lebih lama ).

Gunakan JsonTextReaderkelas dengan StreamReaderatau gunakan JsonSerializeroverload yang secara StreamReaderlangsung:

var serializer = new JsonSerializer();
serializer.Deserialize(streamReader);
Paul Tyng
sumber
23
Cukup yakin ini tidak lagi berfungsi. Anda harus menggunakan JsonReader atau TextReader
BradLaney
8
Anda mungkin ingin memasukkan nomor versi yang masih berfungsi ini agar orang tahu kapan harus menggulir ke bawah.
PoeHaH
@BradLaney yup JsonTextReader (diberikanStreamReader) adalah cara untuk pergi sekarang
Antoine Meltzheim
Terima kasih telah meluangkan waktu untuk mengedit jawaban Anda karena statusnya berfungsi dan rekomendasi jawaban
Nick Bull
281

Versi Json.net saat ini tidak memungkinkan Anda untuk menggunakan kode jawaban yang diterima. Alternatif saat ini adalah:

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

Dokumentasi: Deserialize JSON dari aliran file

James Newton-King
sumber
4
JsonTextReader akan menutup StreamReader secara default, jadi contoh ini bisa disederhanakan sedikit dengan membuat StreamReader dalam panggilan ke konstruktor JsonTextReader.
Oliver Bock
1
Adakah yang tahu bagaimana saya bisa menggunakan konverter kustom bersama dengan kode ini? Tidak melihat cara menentukan konverter yang akan digunakan oleh serializer
alwayslearning
1
Sebenarnya, saya memiliki pengecualian OutOfMemory dan saya sudah menggunakan kode ini, tepatnya. Yang, saya percaya, bisa dikatakan, ini bukan jaminan - jika objek deserialized cukup besar, dan Anda terjebak dalam proses 32-bit, Anda mungkin masih mendapatkan kesalahan memori dengan kode ini
PandaWood
1
saya mendapatkan kesalahan "Tipe atau nama namespace 'JsonTextReader' tidak dapat ditemukan" ... ada saran?
hnvasa
1
Saya perlu menambahkan stream.Position = 0;deserialize json saya dengan benar.
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
sumber
2
Terima kasih! Ini membantu saya menghindari OutOfMemoryException yang saya dapatkan ketika saya membuat serialisasi koleksi objek yang sangat besar ke sebuah string, dan kemudian menulis string itu ke dalam aliran saya (bukan hanya membuat serial langsung ke aliran).
Jon Schneider
2
Mengapa siram Bukankah Buang panggilan yang disebabkan oleh blok penggunaan sudah melakukan itu?
Şafak Gür
bagaimana cara menggunakannya ?
Sana
2
Catatan tambahan, karena dapat membantu orang lain: jika Anda menggunakan, JsonSerializer ser = JsonSerializer.Create(settings);Anda dapat menentukan pengaturan mana yang akan digunakan selama de / serialisasi.
mike
1
Salah satu masalah potensial dengan Serializeimplementasi ini adalah bahwa itu menutup Streamlulus sebagai argumen, yang tergantung pada aplikasi dapat menjadi masalah. Dengan .NET 4.5+ Anda dapat menghindari masalah ini dengan menggunakan StreamWriterkonstruktor yang berlebihan dengan parameter leaveOpenyang memungkinkan Anda membiarkan aliran terbuka.
Joe
29

Saya telah menulis kelas ekstensi untuk membantu saya deserializing dari sumber JSON (string, stream, file).

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

Deserialisasi sekarang semudah menulis:

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

Semoga ini bisa membantu orang lain.

Tok '
sumber
2
Terhadap : itu akan mencemari semua string dengan metode ekstensi. Penanganan Masalah : Hanya nyatakan di Using SomeJsonHelpersNamespacemana diperlukan atau hapus thiskata kunci dan gunakan JsonHelpers.CreateFromJsonString(someJsonString) Pro : ini lebih mudah digunakan :)
Tok '
1
Meskipun itu dapat dilihat sebagai "mencemari", hampir setengah ekstensi dalam objek String dapat dilihat dengan cara yang sama. Ini memperluas objek dengan cara yang dilihat berguna bagi siapa saja yang secara konsisten akan berubah dari string (json) ke JSON.
vipersassassin
Juga menggunakan Encoding.Defaultitu buruk karena akan berperilaku berbeda pada mesin yang berbeda (lihat peringatan besar di Microsoft docu). JSON diharapkan menjadi UTF-8 dan inilah yang diharapkan JsonSerializer. Seharusnya begitu Encoding.UTF8. Kode seperti ini akan menghasilkan string rusak atau gagal deserialize jika karakter non-ASCII digunakan.
ckuri
17

Saya tiba di pertanyaan ini mencari cara untuk melakukan streaming daftar objek terbuka ke System.IO.Streamdan membacanya dari ujung yang lain, tanpa buffering seluruh daftar sebelum mengirim. (Khususnya saya streaming objek tetap dari MongoDB melalui Web API.)

@ Paul Tyng dan @Rivers melakukan pekerjaan yang sangat baik untuk menjawab pertanyaan awal, dan saya menggunakan jawaban mereka untuk membangun bukti konsep untuk masalah saya. Saya memutuskan untuk memposting aplikasi konsol pengujian saya di sini kalau-kalau ada orang lain yang menghadapi masalah yang sama.

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

Perhatikan bahwa Anda mungkin menerima pengecualian saat AnonymousPipeServerStreamdibuang, saya mengabaikannya karena tidak relevan dengan masalah yang dihadapi.

Blake Mitchell
sumber
1
Saya perlu memodifikasi ini sehingga saya bisa mendapatkan objek JSON lengkap. Server dan klien saya berkomunikasi dengan mengirim cuplikan JSON sehingga klien dapat mengirim {"sign in":{"username":"nick"}}{"buy item":{"_id":"32321123"}}dan perlu melihatnya sebagai dua fragmen JSON yang menandakan suatu peristiwa setiap kali ia membaca sebuah fragmen. Dalam nodejs ini dapat dilakukan dalam 3 baris kode.
Nick Sotiros