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

我做错了吗?

236395 次浏览

更新: 这在当前版本中不再有效,请参阅 下面获得正确答案(没有必要投票否决,这是正确的旧版本)。

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

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

当前版本的 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);
}
}

文件: < strong > 从文件流反序列化 JSON

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

希望能帮到别人。

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

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

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

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

你需要创建扩展方法,这是整套方法:

public static class JsonStreamReaderExt
{
static JsonSerializerSettings _settings ;
static JsonStreamReaderExt()
{
_settings = JsonConvert.DefaultSettings?.Invoke() ?? new 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);




}


}