gpt4 book ai didi

c# - 如何使用 System.Text.Json 将 Newtonsoft JToken 序列化为 JSON?

转载 作者:行者123 更新时间:2023-12-04 08:23:30 24 4
gpt4 key购买 nike

在升级到ASP.NET Core 5的过程中,我们遇到了需要序列化并返回一个Json.NET的情况 JObject (由一些我们还不能更改的遗留代码返回)使用 System.Text.Json .如何以合理有效的方式完成此操作,而无需将 JSON 重新序列化和重新解析为 JsonDocument或通过 AddNewtonsoftJson() 完全恢复到 Json.NET ?
具体来说,假设我们有以下遗留数据模型:

public class Model
{
public JObject Data { get; set; }
}
当我们从 ASP.NET Core 5.0 返回它时,“value”属性的内容会被分解成一系列空数组。例如。:
var inputJson = @"{""value"":[[null,true,false,1010101,1010101.10101,""hello"",""𩸽"",""\uD867\uDE3D"",""2009-02-15T00:00:00Z"",""\uD867\uDE3D\u0022\\/\b\f\n\r\t\u0121""]]}";
var model = new Model { Data = JObject.Parse(inputJson) };
var outputJson = JsonSerializer.Serialize(model);

Console.WriteLine(outputJson);

Assert.IsTrue(JToken.DeepEquals(JToken.Parse(inputJson), JToken.Parse(outputJson)[nameof(Model.Data)]));
失败,并生成以下不正确的 JSON:
{"Data":{"value":[[[],[],[],[],[],[],[],[],[],[]]]}}
如何正确序列化 JObject System.Text.Json 的属性(property)?请注意 JObject可能相当大,所以我们宁愿将其流式传输,而不是将其格式化为字符串并从头开始再次解析为 JsonDocument简单地归还它。

最佳答案

需要创建一个custom JsonConverterFactory 序列化 Json.NET JToken使用 System.Text.Json 将层次结构转换为 JSON .
由于该问题旨在避免重新序列化整个 JObject到 JSON 只是为了再次使用 System.Text.Json 解析它,下面的转换器递减 token 层次结构,递归地将每个单独的值写入 Utf8JsonWriter :

using System.Text.Json;
using System.Text.Json.Serialization;
using Newtonsoft.Json.Linq;

public class JTokenConverterFactory : JsonConverterFactory
{
// In case you need to set FloatParseHandling or DateFormatHandling
readonly Newtonsoft.Json.JsonSerializerSettings settings;

public JTokenConverterFactory() { }

public JTokenConverterFactory(Newtonsoft.Json.JsonSerializerSettings settings) => this.settings = settings;

public override bool CanConvert(Type typeToConvert) => typeof(JToken).IsAssignableFrom(typeToConvert);

public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var converterType = typeof(JTokenConverter<>).MakeGenericType(new [] { typeToConvert} );
return (JsonConverter)Activator.CreateInstance(converterType, new object [] { options, settings } );
}

class JTokenConverter<TJToken> : JsonConverter<TJToken> where TJToken : JToken
{
readonly JsonConverter<bool> boolConverter;
readonly JsonConverter<long> longConverter;
readonly JsonConverter<double> doubleConverter;
readonly JsonConverter<decimal> decimalConverter;
readonly JsonConverter<string> stringConverter;
readonly JsonConverter<DateTime> dateTimeConverter;
readonly Newtonsoft.Json.JsonSerializerSettings settings;

public override bool CanConvert(Type typeToConvert) => typeof(TJToken).IsAssignableFrom(typeToConvert);

public JTokenConverter(JsonSerializerOptions options, Newtonsoft.Json.JsonSerializerSettings settings)
{
// Cache some converters for efficiency
boolConverter = (JsonConverter<bool>)options.GetConverter(typeof(bool));
stringConverter = (JsonConverter<string>)options.GetConverter(typeof(string));
longConverter = (JsonConverter<long>)options.GetConverter(typeof(long));
decimalConverter = (JsonConverter<decimal>)options.GetConverter(typeof(decimal));
doubleConverter = (JsonConverter<double>)options.GetConverter(typeof(double));
dateTimeConverter = (JsonConverter<DateTime>)options.GetConverter(typeof(DateTime));
this.settings = settings;
}

public override TJToken Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// This could be substantially optimized for memory use by creating code to read from a Utf8JsonReader and write to a JsonWriter (specifically a JTokenWriter).
// We could just write the JsonDocument to a string, but System.Text.Json works more efficiently with UTF8 byte streams so write to one of those instead.
using var doc = JsonDocument.ParseValue(ref reader);
using var ms = new MemoryStream();
using (var writer = new Utf8JsonWriter(ms))
doc.WriteTo(writer);
ms.Position = 0;
using (var sw = new StreamReader(ms))
using (var jw = new Newtonsoft.Json.JsonTextReader(sw))
{
return Newtonsoft.Json.JsonSerializer.CreateDefault(settings).Deserialize<TJToken>(jw);
}
}

public override void Write(Utf8JsonWriter writer, TJToken value, JsonSerializerOptions options) =>
// Optimize for memory use by descending the JToken hierarchy and writing each one out, rather than formatting to a string, parsing to a `JsonDocument`, then writing that.
WriteCore(writer, value, options);

void WriteCore(Utf8JsonWriter writer, JToken value, JsonSerializerOptions options)
{
if (value == null || value.Type == JTokenType.Null)
{
writer.WriteNullValue();
return;
}

switch (value)
{
case JValue jvalue when jvalue.GetType() != typeof(JValue): // JRaw, maybe others
default: // etc
{
// We could just format the JToken to a string, but System.Text.Json works more efficiently with UTF8 byte streams so write to one of those instead.
using var ms = new MemoryStream();
using (var tw = new StreamWriter(ms, leaveOpen : true))
using (var jw = new Newtonsoft.Json.JsonTextWriter(tw))
{
value.WriteTo(jw);
}
ms.Position = 0;
using var doc = JsonDocument.Parse(ms);
doc.WriteTo(writer);
}
break;
// Hardcode some standard cases for efficiency
case JValue jvalue when jvalue.Value is bool v:
boolConverter.WriteOrSerialize(writer, v, options);
break;
case JValue jvalue when jvalue.Value is string v:
stringConverter.WriteOrSerialize(writer, v, options);
break;
case JValue jvalue when jvalue.Value is long v:
longConverter.WriteOrSerialize(writer, v, options);
break;
case JValue jvalue when jvalue.Value is decimal v:
decimalConverter.WriteOrSerialize(writer, v, options);
break;
case JValue jvalue when jvalue.Value is double v:
doubleConverter.WriteOrSerialize(writer, v, options);
break;
case JValue jvalue when jvalue.Value is DateTime v:
dateTimeConverter.WriteOrSerialize(writer, v, options);
break;
case JValue jvalue:
JsonSerializer.Serialize(writer, jvalue.Value, options);
break;
case JArray array:
{
writer.WriteStartArray();
foreach (var item in array)
WriteCore(writer, item, options);
writer.WriteEndArray();
}
break;
case JObject obj:
{
writer.WriteStartObject();
foreach (var p in obj.Properties())
{
writer.WritePropertyName(p.Name);
WriteCore(writer, p.Value, options);
}
writer.WriteEndObject();
}
break;
}
}
}
}

public static class JsonExtensions
{
public static void WriteOrSerialize<T>(this JsonConverter<T> converter, Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
if (converter != null)
converter.Write(writer, value, options);
else
JsonSerializer.Serialize(writer, value, options);
}
}
那么问题中的单元测试应该修改为使用下面的 JsonSerializerOptions :
var options = new JsonSerializerOptions
{
Converters = { new JTokenConverterFactory() },
};
var outputJson = JsonSerializer.Serialize(model, options);
笔记:
  • 转换器实现JToken的反序列化类型以及序列化,但是由于这不是问题的严格要求,它只是将整个 JSON 层次结构读入 JsonDocument , 输出到 MemoryStream并使用 Json.NET 重新解析它。
  • Newtonsoft 的 JsonSerializerSettings 可以传递给自定义设置,例如 FloatParseHandlingDateFormatHandling在反序列化过程中。
  • 添加JTokenConverterFactory到 ASP.NET Core 序列化选项,请参阅 Configure System.Text.Json-based formatters .

  • 在此处演示一些基本测试: fiddle #1 .
    通过从 Utf8JsonReader 流式传输来实现反序列化的原型(prototype)版本到 JsonWriter无需将整个 JSON 值加载到 JsonDocument 中可以在这里找到: fiddle #2 .

    关于c# - 如何使用 System.Text.Json 将 Newtonsoft JToken 序列化为 JSON?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65386083/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com