dbc*_*dbc 6 c# json json.net system.text.json asp.net-core-5.0
在升级到 ASP.NET Core 5 的过程中,我们遇到了一种情况,需要JObject使用System.Text.Json. 如何以相当有效的方式完成此操作,而不需要将 JSON 重新序列化和重新解析为 aJsonDocument或通过 完全恢复回 Json.NET AddNewtonsoftJson()?
具体来说,假设我们有以下遗留数据模型:
public class Model
{
public JObject Data { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
当我们从 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)]));
Run Code Online (Sandbox Code Playgroud)
失败,并生成以下错误的 JSON:
public class Model
{
public JObject Data { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
如何正确序列化JObject属性System.Text.Json?请注意, 可能JObject相当大,因此我们更愿意将其流式传输,而不是将其格式化为字符串并从头开始再次将其解析为JsonDocument简单地返回它。
dbc*_*dbc 10
有必要创建一个自定义JsonConverterFactoryJToken来使用 .NET 将Json.NET 层次结构序列化为JSON System.Text.Json。
由于问题旨在避免将整个数据重新序列JObject化为 JSON 以便再次使用 进行解析System.Text.Json,因此以下转换器会沿着令牌层次结构向下递归,将每个单独的值写入到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);
}
}
Run Code Online (Sandbox Code Playgroud)
那么问题中的单元测试应该修改为使用以下内容JsonSerializerOptions:
var options = new JsonSerializerOptions
{
Converters = { new JTokenConverterFactory() },
};
var outputJson = JsonSerializer.Serialize(model, options);
Run Code Online (Sandbox Code Playgroud)
笔记:
该转换器实现JToken类型的反序列化以及序列化,但是由于这不是问题的严格要求,它只是将整个 JSON 层次结构读入 a JsonDocument,将其输出到 aMemoryStream并使用 Json.NET 重新解析它。
可以传递NewtonsoftJsonSerializerSettings来自定义设置,例如反序列化FloatParseHandling或DateFormatHandling反序列化期间。
要添加JTokenConverterFactoryASP.NET Core 序列化选项,请参阅配置基于 System.Text.Json 的格式化程序。
此处演示了一些基本测试:fiddle #1。
可以在此处找到通过从 a 流式传输Utf8JsonReader到 aJsonWriter而不将整个 JSON 值加载到 a 中来实现反序列化的原型版本: fiddle #2。JsonDocument
| 归档时间: |
|
| 查看次数: |
5576 次 |
| 最近记录: |