Json.NET能否对流进行序列化/反序列化?

Omr*_*itt 137 .net serialization json.net

我听说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);
Run Code Online (Sandbox Code Playgroud)

我做错了吗?

小智 249

当前版本的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

文档:从文件流中反序列化JSON

  • 我需要添加 `stream.Position = 0;` 来正确反序列化我的 json。 (5认同)
  • 默认情况下,JsonTextReader将关闭其StreamReader,因此可以通过在调用JsonTextReader构造函数中构造StreamReader来简化此示例. (4认同)
  • 实际上,我有一个 OutOfMemory 异常并且我已经使用了这个代码,几乎完全相同。我相信,这不是一个保证——如果反序列化的对象足够大,并且你被困在 32 位进程中,你可能仍然会遇到这段代码的内存错误 (2认同)

yga*_*don 63

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 此“Serialize”实现的一个潜在问题是它关闭了作为参数传递的“Stream”,这可能是一个问题,具体取决于应用程序。在 .NET 4.5+ 中,您可以通过使用带有参数“leaveOpen”的“StreamWriter”构造函数重载来避免此问题,该参数允许您使流保持打开状态。 (5认同)
  • 谢谢!这帮助我避免了在将一个非常大的对象集合序列化为一个字符串,然后将该字符串写入我的流(而不是直接序列化到该流)时遇到的OutOfMemoryException。 (2认同)
  • 为什么要冲洗?由using块引起的Dispose调用是否已经做到了? (2认同)
  • 旁注,因为它可能对其他人有帮助:如果使用`JsonSerializer ser = JsonSerializer.Create(settings);`,则可以定义在反序列化期间要使用的设置。 (2认同)

Pau*_*yng 50

更新:这在当前版本中不再有效,请参阅下面的正确答案(无需拒绝投票,这在旧版本中是正确的).

使用JsonTextReader带有a 的类StreamReader或使用直接带有的JsonSerializer重载StreamReader:

var serializer = new JsonSerializer();
serializer.Deserialize(streamReader);
Run Code Online (Sandbox Code Playgroud)

  • 很确定这不再适用.您必须使用JsonReader或TextReader (23认同)
  • 您可能希望包含仍在处理的版本号,以便人们知道何时向下滚动. (6认同)
  • “无需否决,这在旧版本上是正确的” - 它不再与大多数人相关,因此应该将其否决,因为存在更好的答案。 (3认同)

Tok*_*ok' 28

我编写了一个扩展类来帮助我从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;
    }
}
Run Code Online (Sandbox Code Playgroud)

反序列化现在和编写一样简单:

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

希望它会帮助别人.

  • 像这样扩展String对象真的是个好主意吗? (4认同)
  • *反对*:它将使用扩展方法污染所有字符串.*变通方法*:只在必要时声明`使用SomeJsonHelpersNamespace`或删除`this`关键字并使用`JsonHelpers.CreateFromJsonString(someJsonString)`*Pro*:它更容易使用:) (2认同)
  • 尽管它可能被视为“污染”,但几乎可以以相同方式看待 String 对象中的一半扩展。这以一种被视为对任何人都将始终从 string(json) 更改为 JSON 有用的方式扩展对象。 (2认同)

Bla*_*ell 12

我到达了这个问题,寻找一种方法将一个开放的对象列表流式传输到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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,处理时可能会收到异常AnonymousPipeServerStream,我忽略了这一点,因为它与手头的问题无关.