Json.NET สามารถทำให้เป็นอนุกรม / ดีซีเรียลไลซ์เป็น / จากกระแสได้หรือไม่?


151

ฉันได้ยินมาว่า Json.NET เร็วกว่า DataContractJsonSerializer และต้องการลอง ...

แต่ฉันไม่พบวิธีการใด ๆ ใน JsonConvert ที่รับกระแสข้อมูลแทนที่จะเป็นสายอักขระ

สำหรับการดีซีเรียลไลซ์ไฟล์ที่มี JSON บน WinPhone ฉันใช้รหัสต่อไปนี้เพื่ออ่านเนื้อหาของไฟล์ลงในสายอักขระแล้วจึงทำการ deserialize ให้เป็น JSON ดูเหมือนว่าจะช้ากว่าการทดสอบของฉัน (เฉพาะกิจมาก) ประมาณ 4 เท่าเมื่อเทียบกับการใช้ DataContractJsonSerializer เพื่อทำการ deserialize โดยตรงจากสตรีม ...

// 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) เป็นวิธีที่จะไปตอนนี้
Antoine Meltzheim

ขอขอบคุณที่สละเวลาแก้ไขคำตอบของคุณเนื่องจากสถานะการทำงานและคำแนะนำคำตอบ
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);
    }
}

เอกสาร: Deserialize JSON จากสตรีมไฟล์


4
JsonTextReader จะปิด StreamReader ของมันตามค่าเริ่มต้นดังนั้นตัวอย่างนี้สามารถทำให้ง่ายขึ้นเล็กน้อยโดยการสร้าง StreamReader ในการเรียกตัวสร้าง JsonTextReader
Oliver Bock

1
มีความคิดใดบ้างที่ฉันสามารถใช้ตัวแปลงที่กำหนดเองพร้อมกับรหัสนี้ ดูวิธีการระบุตัวแปลงเพื่อนำมาใช้โดย serializer ไม่มี
alwayslearning

1
ที่จริงแล้วฉันมีข้อยกเว้น OutOfMemory และฉันใช้รหัสนี้ไปแล้ว ซึ่งฉันเชื่อว่าจะบอกว่านี่ไม่ใช่การรับประกันถ้าวัตถุที่ดีซีเรียลไลซ์มีขนาดใหญ่พอและคุณติดอยู่ในกระบวนการ 32 บิตคุณอาจยังคงได้รับข้อผิดพลาดของหน่วยความจำด้วยรหัสนี้
PandaWood

1
ฉันได้รับข้อผิดพลาด "ไม่พบชื่อประเภทหรือเนมสเปซ 'JsonTextReader'" ... คำแนะนำใด ๆ
hnvasa

1
ฉันต้องการที่จะเพิ่มstream.Position = 0;เพื่อ deserialize 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 ที่ฉันได้รับเมื่อฉันจัดลำดับวัตถุคอลเลกชันที่มีขนาดใหญ่มากให้เป็นสตริงจากนั้นจึงเขียนสตริงนั้นลงในสตรีมของฉัน
Jon Schneider

2
ทำไมต้องล้างออก การจัดการการโทรที่เกิดจากการใช้บล็อคไม่ได้ทำอย่างนั้นหรือ
ŞafakGür

วิธีใช้งาน
Sana

2
หมายเหตุด้านข้างเนื่องจากอาจช่วยผู้อื่น: หากคุณใช้JsonSerializer ser = JsonSerializer.Create(settings);คุณสามารถกำหนดการตั้งค่าที่จะใช้ในระหว่างการ de / การทำให้เป็นอนุกรม
ไมค์

1
ปัญหาที่อาจเกิดขึ้นกับSerializeการใช้งานนี้คือมันปิดการStreamส่งผ่านเป็นอาร์กิวเมนต์ซึ่งขึ้นอยู่กับแอปพลิเคชันอาจเป็นปัญหา ด้วย. NET 4.5+ คุณสามารถหลีกเลี่ยงปัญหานี้ได้โดยใช้StreamWriterConstructor Overload พร้อมพารามิเตอร์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;
    }
}

การเลิกทำการ Deserializing นั้นง่ายเหมือนการเขียน:

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 docu) 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 บรรทัด
Nick Sotiros
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.