Json.NET可以序列化到流或从流反序列化吗?


151

我听说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);

我做错了吗?

Answers:


58

更新:这在当前版本中不再起作用,请参见下面的正确答案(无需否决,这在较早版本上是正确的)。

JsonTextReader类与a一起StreamReader使用,或JsonSerializerStreamReader直接使用a的重载使用:

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

23
相当确定这不再起作用。您必须使用JsonReader或TextReader
BradLaney 2012年

8
您可能需要包括仍在使用的版本号,以便人们知道何时向下滚动。
PoeHaH

@BradLaney yup JsonTextReader(givenStreamReader)是现在要走的路
Antoine Meltzheim,

感谢您抽出来编辑你的答案的时候再它的工作状态并回答推荐
尼克公牛

280

当前版本的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
知道如何将自定义转换器与此代码一起使用吗?看不到指定要由串行器使用的转换器的方法
alwayslearning'Aug

1
实际上,我有一个OutOfMemory异常,并且我已经完全使用了此代码。我相信这是不能保证的-如果反序列化的对象足够大,并且您陷入了32位进程中,则此代码可能仍然会出现内存错误
PandaWood

1
我收到错误消息“找不到类型或名称空间名称'JsonTextReader'” ...任何建议?
hnvasa

1
我需要添加stream.Position = 0;以正确反序列化我的json。
hybrid2102 '19

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
为什么要冲洗?由using块引起的Dispose调用是否已经做到了?
Şafak古尔

如何使用它 ?
萨那

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'17年

1
尽管可以将其视为“污染”,但可以用相同的方式看到String对象中几乎一半的扩展。这以一种对任何从string(json)始终更改为JSON的人都有用的方式扩展了对象。
vipersassassin 18-4-3

同时使用Encoding.Default也是不好的,因为它在不同的机器上表现会有所不同(请参阅Microsoft文档上的警告)。JSON预期为UTF-8,这就是JsonSerializer所期望的。因此应该如此Encoding.UTF8。如果使用非ASCII字符,则按原样的代码将产生乱码或无法反序列化。
ckuri

17

我遇到了这个问题,正在寻找一种方法来将开放式对象列表流式传输到上System.IO.Stream,然后从另一端读取它们,而不在发送前对整个列表进行缓冲。(特别是我正在通过Web API从MongoDB流式传输持久对象。)

@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行代码来完成。
尼克·索提罗斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.