Json.NET có thể tuần tự hóa / giải tuần tự hóa đến / từ một luồng không?


151

Tôi đã nghe nói rằng Json.NET nhanh hơn DataContractJsonSerializer và muốn dùng thử ...

Nhưng tôi không thể tìm thấy bất kỳ phương thức nào trên JsonConvert có một luồng hơn là một chuỗi.

Ví dụ, để giải tuần tự hóa một tệp chứa JSON trên WinPhone, tôi sử dụng đoạn mã sau để đọc nội dung tệp thành một chuỗi, sau đó giải tuần tự hóa thành JSON. Nó dường như chậm hơn khoảng 4 lần trong thử nghiệm (rất đặc biệt) của tôi so với việc sử dụng DataContractJsonSerializer để giải tuần tự trực tiếp từ luồng ...

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

Tôi đang làm sai à?

Câu trả lời:


58

CẬP NHẬT: Điều này không còn hoạt động trong phiên bản hiện tại, xem bên dưới để có câu trả lời chính xác ( không cần bỏ phiếu xuống, điều này là chính xác trên các phiên bản cũ hơn ).

Sử dụng JsonTextReaderlớp với một StreamReaderhoặc sử dụng JsonSerializerquá tải mất StreamReadertrực tiếp:

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

23
Khá chắc chắn rằng điều này không còn hoạt động. Bạn phải sử dụng JsonReader hoặc TextReader
BradLaney

8
Bạn có thể muốn bao gồm số phiên bản này vẫn đang hoạt động để mọi người biết khi nào cần cuộn xuống.
PoeHaH

@BradLaney yup JsonTextReader (giveStreamReader) là cách để đi ngay bây giờ
Antoine Meltzheim

Cảm ơn bạn đã dành thời gian để chỉnh sửa câu trả lời của mình, đó là trạng thái làm việc và đề xuất trả lời
Nick Bull

281

Phiên bản hiện tại của Json.net không cho phép bạn sử dụng mã câu trả lời được chấp nhận. Một thay thế hiện tại là:

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

Tài liệu: Deserialize JSON từ một luồng tệp


4
JsonTextReader sẽ đóng StreamReader của nó theo mặc định, vì vậy ví dụ này có thể được đơn giản hóa một chút bằng cách xây dựng StreamReader trong lệnh gọi đến hàm tạo JsonTextReader.
Oliver Bock

1
Bất kỳ ý tưởng làm thế nào tôi có thể sử dụng một trình chuyển đổi tùy chỉnh cùng với mã này? Xem không có cách nào để chỉ định một trình chuyển đổi được sử dụng bởi serializer
luôn luôn học

1
Trên thực tế, tôi có một ngoại lệ OutOfMemory và tôi đã sử dụng mã này, khá chính xác. Theo tôi, điều mà tôi tin rằng, đây không phải là một sự đảm bảo - nếu đối tượng khử lưu lượng đủ lớn và bạn bị mắc kẹt trong quy trình 32 bit, bạn vẫn có thể gặp lỗi bộ nhớ với mã này
PandaWood

1
tôi đang gặp lỗi "Không thể tìm thấy tên loại hoặc không gian tên 'JsonTextReader'" ... có đề xuất nào không?
hnvasa

1
Tôi cần phải thêm stream.Position = 0;để khử chính xác json của tôi.
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
Cảm ơn! Điều này giúp tôi tránh OutOfMemoryException mà tôi nhận được khi tôi đang tuần tự hóa một bộ sưu tập đối tượng rất lớn thành một chuỗi, sau đó viết chuỗi đó vào luồng của tôi (thay vì chỉ nối tiếp trực tiếp vào luồng).
Jon Schneider

2
Tại sao phải xả nước? Không phải cuộc gọi Vứt bỏ do khối sử dụng đã làm điều đó sao?
Şafak Gür

Làm thế nào để sử dụng nó ?
Sana

2
Lưu ý bên cạnh, bởi vì nó có thể giúp người khác: nếu bạn sử dụng, JsonSerializer ser = JsonSerializer.Create(settings);bạn có thể xác định cài đặt nào sẽ sử dụng trong quá trình khử / tuần tự hóa.
mike

1
Một vấn đề tiềm năng với việc Serializetriển khai này là nó đóng Streamthông qua dưới dạng đối số, tùy thuộc vào ứng dụng có thể là một vấn đề. Với .NET 4.5+, bạn có thể tránh sự cố này bằng cách sử dụng StreamWriterquá tải hàm tạo với tham số leaveOpencho phép bạn để luồng mở.
Joe

29

Tôi đã viết một lớp mở rộng để giúp tôi giải tuần tự hóa từ các nguồn JSON (chuỗi, luồng, tệp).

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

Việc khử lưu huỳnh bây giờ dễ như viết:

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

Hy vọng nó sẽ giúp người khác.


2
Chống lại : nó sẽ gây ô nhiễm tất cả các chuỗi với các phương thức mở rộng. Giải pháp thay thế : Chỉ khai báo Using SomeJsonHelpersNamespacekhi cần hoặc xóa thistừ khóa và sử dụng JsonHelpers.CreateFromJsonString(someJsonString) Pro : nó rất dễ sử dụng :)
Tok

1
Mặc dù nó có thể được coi là "gây ô nhiễm", gần một nửa các phần mở rộng trong đối tượng String có thể được nhìn thấy theo cùng một cách. Điều này mở rộng một đối tượng theo cách được coi là hữu ích cho bất kỳ ai luôn thay đổi từ chuỗi (json) sang JSON.
vipersassassin

Ngoài ra sử dụng Encoding.Defaultlà xấu vì nó sẽ hoạt động khác nhau trên các máy khác nhau (xem cảnh báo lớn tại Microsoft docu). JSON dự kiến ​​sẽ là UTF-8 và đây là những gì JsonSerializer mong đợi. Vì vậy, nó nên được Encoding.UTF8. Mã như sẽ tạo ra các chuỗi bị cắt xén hoặc không thể khử lưu lượng nếu các ký tự không phải ASCII được sử dụng.
ckuri

17

Tôi đến câu hỏi này để tìm cách truyền một danh sách các đối tượng kết thúc mở lên System.IO.Streamvà đọc chúng từ đầu kia, mà không đệm toàn bộ danh sách trước khi gửi. (Cụ thể tôi đang phát trực tuyến các đối tượng từ MongoDB qua API Web.)

@Paul Tyng và @Rivers đã làm một công việc tuyệt vời để trả lời câu hỏi ban đầu và tôi đã sử dụng câu trả lời của họ để xây dựng một bằng chứng về khái niệm cho vấn đề của mình. Tôi quyết định đăng ứng dụng bảng điều khiển thử nghiệm của mình ở đây trong trường hợp bất kỳ ai khác đang gặp phải vấn đề tương tự.

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

Lưu ý rằng bạn có thể nhận được một ngoại lệ khi AnonymousPipeServerStreamxử lý, tôi bỏ qua điều này vì nó không liên quan đến vấn đề trong tay.


1
Tôi cần sửa đổi điều này để tôi có thể nhận được bất kỳ đối tượng JSON hoàn chỉnh nào. Máy chủ và máy khách của tôi liên lạc bằng cách gửi các đoạn JSON để khách hàng có thể gửi {"sign in":{"username":"nick"}}{"buy item":{"_id":"32321123"}}và nó cần phải xem đây là hai đoạn JSON báo hiệu một sự kiện mỗi khi nó đọc một đoạn. Trong nodejs điều này có thể được thực hiện trong 3 dòng mã.
Nick Sotiros
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.