【问题标题】:Can Json.NET serialize / deserialize to / from a stream?Json.NET 可以对流进行序列化/反序列化吗?
【发布时间】:2011-12-30 18:05:51
【问题描述】:

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

我做错了吗?

【问题讨论】:

    标签: .net serialization json.net


    【解决方案1】:

    当前版本的 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 from a file stream

    【讨论】:

    • JsonTextReader 默认会关闭它的 StreamReader,所以这个例子可以通过在调用 JsonTextReader 构造函数时构造 StreamReader 来稍微简化一下。
    • 知道如何将自定义转换器与此代码一起使用吗?看不到指定序列化程序要使用的转换器的方法
    • 实际上,我有一个 OutOfMemory 异常,我已经使用了这段代码,几乎完全一样。我相信,这不能保证 - 如果反序列化的对象足够大,并且您被困在 32 位进程中,则此代码可能仍然会出现内存错误
    • 我收到一个错误“找不到类型或命名空间名称'JsonTextReader'”...有什么建议吗?
    • 我需要添加stream.Position = 0; 才能正确反序列化我的json。
    【解决方案2】:
    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);
        }
    }
    

    【讨论】:

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

    更新:这在当前版本中不再有效,请参阅below 以获得正确答案(无需投票,这在旧版本上是正确的)。

    使用带有StreamReaderJsonTextReader 类或直接使用带有StreamReaderJsonSerializer 重载:

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

    【讨论】:

    • 很确定这不再有效。您必须使用 JsonReader 或 TextReader
    • 您可能希望包含仍在使用的版本号,以便人们知道何时向下滚动。
    • @BradLaney 是的 JsonTextReader(givenStreamReader) 是现在要走的路
    • 感谢您抽出宝贵时间来编辑您的答案,了解它的工作状态和答案建议
    • “无需投反对票,这在旧版本上是正确的” - 它与大多数人不再相关,因此应该投反对票,因为存在更好的答案。
    【解决方案4】:

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

    希望对其他人有所帮助。

    【讨论】:

    • 反对:它会用扩展方法污染所有字符串。 解决方法:仅在需要时声明Using SomeJsonHelpersNamespace 或删除this 关键字并使用JsonHelpers.CreateFromJsonString(someJsonString) Pro:它更容易使用:)
    • 虽然它可以被视为“污染”,但几乎一半的 String 对象中的扩展都可以以相同的方式看到。这以一种对始终从 string(json) 更改为 JSON 的任何人都有用的方式扩展对象。
    • 另外使用Encoding.Default 是不好的,因为它在不同的机器上会表现不同(请参阅 Microsoft 文档中的大警告)。 JSON 预计为 UTF-8,这正是 JsonSerializer 所期望的。因此它应该是Encoding.UTF8。如果使用非 ASCII 字符,原样的代码将产生乱码或无法反序列化。
    【解决方案5】:

    我遇到了这个问题,寻找一种将开放式对象列表流式传输到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 被释放时,您可能会收到异常,我忽略了这一点,因为它与手头的问题无关。

    【讨论】:

    • 我需要修改它,这样我才能得到任何完整的 JSON 对象。我的服务器和客户端通过发送 JSON 的 sn-ps 进行通信,因此客户端可以发送 {"sign in":{"username":"nick"}}{"buy item":{"_id":"32321123"}} 并且它需要将其视为 JSON 的两个片段,每次读取一个片段时都会发出一个事件信号。在 nodejs 中,这可以在 3 行代码中完成。
    【解决方案6】:

    当内存不足时另一个方便的选择是定期刷新

    /// <summary>serialize the value in the stream.</summary>
    /// <typeparam name="T">the type to serialize</typeparam>
    /// <param name="stream">The stream.</param>
    /// <param name="value">The value.</param>
    /// <param name="settings">The json settings to use.</param>
    /// <param name="bufferSize"></param>
    /// <param name="leaveOpen"></param>
    public static void JsonSerialize<T>(this Stream stream,[DisallowNull] T value, [DisallowNull] JsonSerializerSettings settings, int bufferSize=1024, bool leaveOpen=false)
    {
        using (var writer = new StreamWriter(stream,encoding: System.Text.Encoding.UTF32,bufferSize,leaveOpen))
        using (var jsonWriter = new JsonTextWriter(writer))
        {
            var ser = JsonSerializer.Create( settings );
            ser.Serialize(jsonWriter, value);
            jsonWriter.Flush();
        }
    }
    
    /// <summary>serialize the value in the stream asynchronously.</summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="stream">The stream.</param>
    /// <param name="value">The value.</param>
    /// <param name="settings">The settings.</param>
    /// <param name="bufferSize">The buffer size, in bytes, set -1 to not flush till done</param>
    /// <param name="leaveOpen"> true to leave the stream open </param>
    /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
    public static Task JsonSerializeAsync<T>(this Stream stream,[DisallowNull] T value, [DisallowNull] JsonSerializerSettings settings, int bufferSize=1024, bool leaveOpen=false, CancellationToken cancellationToken=default)
    {
        using (var writer = new StreamWriter(stream,encoding: System.Text.Encoding.UTF32,bufferSize: bufferSize,leaveOpen: leaveOpen))
        using (var jsonWriter = new JsonTextWriter(writer))
        {
            var ser = JsonSerializer.Create( settings );
            ser.Serialize(jsonWriter, value);
            return  jsonWriter.Flush();
        }
        //jsonWriter.FlushAsnc with my version gives an error on the stream
        return Task.CompletedTask;
    }
    

    你可以像这样测试/使用它:

    [TestMethod()]
    public void WriteFileIntoJsonTest()
    {
        var file = new FileInfo(Path.GetTempFileName());
        try
        {
            var list = new HashSet<Guid>();
            for (int i = 0; i < 100; i++)
            {
                list.Add(Guid.NewGuid());
            }
            file.JsonSerialize(list);
    
    
            var sr = file.IsValidJson<List<Guid>>(out var result);
            Assert.IsTrue(sr);
            Assert.AreEqual<int>(list.Count, result.Count);
            foreach (var item in result)
            {
                Assert.IsFalse(list.Add(item), $"The GUID {item} should already exist in the hash set");
            }
        }
        finally
        {
            file.Refresh();
            file.Delete();
        }
    }
    

    您需要创建扩展方法,这里是整个集合: 公共静态类 JsonStreamReaderExt { 静态 JsonSerializerSettings _settings ; 静态 JsonStreamReaderExt() { _settings = JsonConvert.DefaultSettings?.Invoke() ??新的 JsonSerializerSettings(); _settings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor; _settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; _settings.DateFormatHandling = DateFormatHandling.IsoDateFormat ; }

        /// <summary>
        /// serialize the value in the stream.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream">The stream.</param>
        /// <param name="value">The value.</param>
        public static void JsonSerialize<T>(this Stream stream,[DisallowNull] T value)
        {
            stream.JsonSerialize(value,_settings);
        }
    
        /// <summary>
        /// serialize the value in the file .
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="file">The file.</param>
        /// <param name="value">The value.</param>
        public static void JsonSerialize<T>(this FileInfo file,[DisallowNull] T value)
        {
            if (string.IsNullOrEmpty(file.DirectoryName)==true && Directory.Exists(file.DirectoryName) == false)
            { 
                Directory.CreateDirectory(file.FullName);
            }
            
            using var s = file.OpenWrite();
            s.JsonSerialize(value, _settings);
    
            file.Refresh();
        }
    
        /// <summary>
        /// serialize the value in the file .
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="file">The file.</param>
        /// <param name="value">The value.</param>
        /// <param name="settings">the json settings to use</param>
        public static void JsonSerialize<T>(this FileInfo file, [DisallowNull] T value, [DisallowNull] JsonSerializerSettings settings)
        {
            if (string.IsNullOrEmpty(file.DirectoryName) == true && Directory.Exists(file.DirectoryName) == false)
            {
                Directory.CreateDirectory(file.FullName);
            }
    
            using var s = file.OpenWrite();
            s.JsonSerialize(value, settings);
    
            file.Refresh();
        }
    
        /// <summary>
        /// serialize the value in the file .
        /// </summary>
        /// <remarks>File will be refreshed to contain the new meta data</remarks>
        /// <typeparam name="T">the type to serialize</typeparam>
        /// <param name="file">The file.</param>
        /// <param name="value">The value.</param>        
        /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
        public static async Task JsonSerializeAsync<T>(this FileInfo file, [DisallowNull] T value, CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrEmpty(file.DirectoryName) == true && Directory.Exists(file.DirectoryName) == false)
            {
                Directory.CreateDirectory(file.FullName);
            }
    
            using (var stream = file.OpenWrite())
            {
                await stream.JsonSerializeAsync(value, _settings,bufferSize:1024,leaveOpen:false, cancellationToken).ConfigureAwait(false);
            }
            file.Refresh();
        }
        /// <summary>
        /// serialize the value in the file .
        /// </summary>
        /// <remarks>File will be refreshed to contain the new meta data</remarks>
        /// <typeparam name="T">the type to serialize</typeparam>
        /// <param name="file">The file to create or overwrite.</param>
        /// <param name="value">The value.</param>
        /// <param name="settings">the json settings to use</param>
        /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
        public static async Task JsonSerializeAsync<T>(this FileInfo file, [DisallowNull] T value, [DisallowNull] JsonSerializerSettings settings, CancellationToken cancellationToken=default)
        {
            if (string.IsNullOrEmpty(file.DirectoryName) == true && Directory.Exists(file.DirectoryName) == false)
            {
                Directory.CreateDirectory(file.FullName);
            }
    
            using (var stream = file.OpenWrite())
            {
                await stream.JsonSerializeAsync(value, settings,bufferSize:1024,leaveOpen:false, cancellationToken).ConfigureAwait(false);
            }
            file.Refresh();
        }
    
        /// <summary>serialize the value in the stream.</summary>
        /// <typeparam name="T">the type to serialize</typeparam>
        /// <param name="stream">The stream.</param>
        /// <param name="value">The value.</param>
        /// <param name="settings">The json settings to use.</param>
        /// <param name="bufferSize">The buffer size, in bytes, set -1 to not flush till done</param>
        /// <param name="leaveOpen"> true to leave the stream open </param>
        public static void JsonSerialize<T>(this Stream stream,[DisallowNull] T value, [DisallowNull] JsonSerializerSettings settings, int bufferSize=1024, bool leaveOpen=false)
        {
            using (var writer = new StreamWriter(stream,encoding: System.Text.Encoding.UTF32,bufferSize,leaveOpen))
            using (var jsonWriter = new JsonTextWriter(writer))
            {
                var ser = JsonSerializer.Create( settings );
                ser.Serialize(jsonWriter, value);
                jsonWriter.Flush();
            }
        }
    
        /// <summary>serialize the value in the stream asynchronously.</summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream">The stream.</param>
        /// <param name="value">The value.</param>
        /// <param name="settings">The settings.</param>
        /// <param name="bufferSize">The buffer size, in bytes, set -1 to not flush till done</param>
        /// <param name="leaveOpen"> true to leave the stream open </param>
        /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
        public static Task JsonSerializeAsync<T>(this Stream stream,[DisallowNull] T value, [DisallowNull] JsonSerializerSettings settings, int bufferSize=1024, bool leaveOpen=false, CancellationToken cancellationToken=default)
        {
            using (var writer = new StreamWriter(stream,encoding: System.Text.Encoding.UTF32,bufferSize: bufferSize,leaveOpen: leaveOpen))
            using (var jsonWriter = new JsonTextWriter(writer))
            {
                var ser = JsonSerializer.Create( settings );
                ser.Serialize(jsonWriter, value);
                jsonWriter.Flush();
            }
            return Task.CompletedTask;
           
        }
    
    
    
        /// <summary>
        /// Determines whether [is valid json] [the specified result].
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream">The stream.</param>
        /// <param name="result">The result.</param>
        /// <returns><c>true</c> if [is valid json] [the specified result]; otherwise, <c>false</c>.</returns>
        public static bool IsValidJson<T>(this Stream stream, [NotNullWhen(true)] out T? result)
        {
            if (stream is null)
            {
                throw new ArgumentNullException(nameof(stream));
            }
    
            if (stream.Position != 0)
            {
                stream.Seek(0, SeekOrigin.Begin);
            }
            JsonSerializerSettings settings = (JsonConvert.DefaultSettings?.Invoke()) ?? new JsonSerializerSettings() { DateTimeZoneHandling = DateTimeZoneHandling.Utc, DateFormatHandling = DateFormatHandling.IsoDateFormat };
            settings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor;
            using (var reader = new StreamReader(stream))
            using (var jsonReader = new JsonTextReader(reader))
            {
                var ser = JsonSerializer.Create(settings);
                try
                {
                    result = ser.Deserialize<T>(jsonReader);
                }
                catch { result = default; }
            }
            return result is not null;
        }
        /// <summary>
        /// Determines whether [is valid json] [the specified settings].
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream">The stream.</param>
        /// <param name="settings">The settings.</param>
        /// <param name="result">The result.</param>
        /// <returns><c>true</c> if [is valid json] [the specified settings]; otherwise, <c>false</c>.</returns>
        public static bool IsValidJson<T>(this Stream stream, JsonSerializerSettings settings, [NotNullWhen(true)] out T? result)
        {
            if (stream is null)
            {
                throw new ArgumentNullException(nameof(stream));
            }
    
            if (settings is null)
            {
                throw new ArgumentNullException(nameof(settings));
            }
    
            if (stream.Position != 0)
            {
                stream.Seek(0, SeekOrigin.Begin);
            }
            using (var reader = new StreamReader(stream))
            using (var jsonReader = new JsonTextReader(reader))
            {
                var ser = JsonSerializer.Create(settings);
                try
                {
                    result = ser.Deserialize<T>(jsonReader);
                }
                catch { result = default; }
            }
            return result is not null;
        }
        /// <summary>
        /// Determines whether file contains valid json using the specified settings and reads it into the output.
        /// </summary>
        /// <typeparam name="T">Type to convert into</typeparam>
        /// <param name="file">The file.</param>
        /// <param name="settings">The settings.</param>
        /// <param name="result">The result.</param>
        /// <returns><c>true</c> if [is valid json] [the specified settings]; otherwise, <c>false</c>.</returns>
        /// <exception cref="System.ArgumentNullException">file</exception>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ArgumentNullException">settings</exception>
        /// <exception cref="System.IO.FileNotFoundException">File could not be accessed</exception>
        public static bool IsValidJson<T>(this FileInfo file, JsonSerializerSettings settings, [NotNullWhen(true)] out T? result)
        {
            if (file is null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (File.Exists(file.FullName) == false)
            { 
                throw new FileNotFoundException("File could not be accessed",fileName: file.FullName);
            }
    
            using var stream = file.OpenRead();
    
            if (stream is null)
            {
                throw new ArgumentNullException(message:"Could not open the file and access the underlying file stream",paramName: nameof(file));
            }
    
    
    
            if (settings is null)
            {
                throw new ArgumentNullException(nameof(settings));
            }
    
    
            using (var reader = new StreamReader(stream))
            using (var jsonReader = new JsonTextReader(reader))
            {
                var ser = JsonSerializer.Create(settings);
                try
                {
                    result = ser.Deserialize<T>(jsonReader);
                }
                catch { result = default; }
            }
            return result is not null;
        }
        /// <summary>
        /// Determines whether file contains valid json using the specified settings and reads it into the output.
        /// </summary>
        /// <typeparam name="T">Type to convert into</typeparam>
        /// <param name="file">The file.</param>
        /// <param name="result">The result.</param>
        /// <returns><c>true</c> if [is valid json] [the specified result]; otherwise, <c>false</c>.</returns>
        /// <exception cref="System.ArgumentNullException">file</exception>
        /// <exception cref="System.IO.FileNotFoundException">File could not be accessed</exception>
        public static bool IsValidJson<T>(this FileInfo file, [NotNullWhen(true)] out T? result)
        {
            if (file is null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (File.Exists(file.FullName) == false)
            { 
                throw new FileNotFoundException("File could not be accessed",fileName: file.FullName);
            }
    
            JsonSerializerSettings settings =( JsonConvert.DefaultSettings?.Invoke()) ?? new JsonSerializerSettings() { DateTimeZoneHandling= DateTimeZoneHandling.Utc, DateFormatHandling= DateFormatHandling.IsoDateFormat };
            settings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor;
            return file.IsValidJson<T>(settings,out result);
    
    
        }
    
    } 
    

    【讨论】:

      猜你喜欢
      • 2016-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-09
      • 1970-01-01
      • 2012-12-18
      • 1970-01-01
      相关资源
      最近更新 更多