目录

介绍

用户故事5:在System.Text.Json JsonSerializer中支持动态类型

演示项目和测试

修改模型方法

包装方法

总结


Pro Coders团队最近将一个大型项目从Newtonsoft迁移到System.Text.Json序列化器,并且由于它不支持使用$type属性进行动态对象反序列化,因此我们实现了一种方法,用于对动态对象进行序列化和反序列化,并注入ModelFullName属性来通过模型类型显式反序列化。

  • Github下载完整的解决方案

介绍

欢迎来到我为C#开发人员撰写的新文章。今天,我想考虑json序列化。最近,Microsoft将其针对WEB API的默认序列化从Newtonsoft JsonConvert更改为System.Text.Json JsonSerializer,并且开发人员意识到不再支持一项重要功能。我说的是Newtonsoft JsonConvert可以在对象序列化期间为每种复杂类型添加的“$type”属性,并使用它反序列化对象。

如果使用以下序列化设置:

var settings = new Newtonsoft.Json.JsonSerializerSettings
{TypeNameAssemblyFormatHandling = Newtonsoft.Json.TypeNameAssemblyFormatHandling.Simple,TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects,
};

并尝试序列化Model属性中的MyModel对象的MyState对象:

public class MyModel
{public string FirstName { get; set; }public string LastName { get; set; }public DateTime BirthDate { get; set; }
}public class MyState
{public int Id { get; set; }public string Name { get; set; }public bool IsReady { get; set; }public DateTime LastUpdated { get; set; }public object Model { get; set; }
}

Newtonsoft JsonConvert将创建一个具有上述“$type”属性的json对象:

{"$type": "DemoSystemTextJson.Tests.MyState, DemoSystemTextJson.Tests","Id": 11,"Name": "CurrentState","IsReady": true,"LastUpdated": "2015-10-21T00:00:00","Model": {"$type": "DemoSystemTextJson.Tests.MyModel, DemoSystemTextJson.Tests","FirstName": "Alex","LastName": "Brown","BirthDate": "1990-01-12T00:00:00"}
}

如您所见,该"$type"属性已添加,用于在反序列化期间帮助识别类型。

还必须注意,该"$type"属性位于每个对象的第一项,否则,Newtonsoft JsonConvert将无法识别它。

具有PostgreSQL使用经验的开发人员可能会注意到,当您将json存储在Postgres数据库中并读回时,属性将没有以前的顺序,并且将以某种方式进行排序——这是因为Postgres将json对象存储为键值对进行内部优化。从Postgres读取此json时,可以得到它:

{"Id": 11,"Name": "CurrentState","IsReady": true,"LastUpdated": "2015-10-21T00:00:00","Model": {"FirstName": "Alex","LastName": "Brown","BirthDate": "1990-01-12T00:00:00","$type": "DemoSystemTextJson.Tests.MyModel, DemoSystemTextJson.Tests"},"$type": "DemoSystemTextJson.Tests.MyState, DemoSystemTextJson.Tests"
}

Newtonsoft JsonConvert将无法识别它。

为了处理WEB API和PostgreSQL,我们将结合使用System.Text.Json JsonSerializer和一些真正的程序员可能会添加到其代码中的魔术,让我们创建一个用户故事。

用户故事5:在System.Text.Json JsonSerializer中支持动态类型

  • 创建一个类,允许对包含未知类型属性的对象进行序列化和反序列化
  • json属性的顺序不应影响反序列化过程

演示项目和测试

为了开始实现用户故事,我将创建一个DemoSystemTextJson类库(.NET Core),并添加xUnit测试项目(.NET Core)—— DemoSystemTextJson.Tests。

我更喜欢从编写测试开始,首先,我们需要将要序列化和反序列化的Model类,让我们将它们添加到测试项目中:

using System;
using System.Collections.Generic;
using System.Text;namespace DemoSystemTextJson.Tests
{public class MyModel{public string FirstName { get; set; }public string LastName { get; set; }public DateTime BirthDate { get; set; }}public class MyState{public int Id { get; set; }public string Name { get; set; }public bool IsReady { get; set; }public DateTime LastUpdated { get; set; }public object Model { get; set; }}
}

有了这些类,我们可以创建第一个测试,该测试将检查是否可以立即反序列化MyState:

using System;
using Xunit;
using System.Text.Json;namespace DemoSystemTextJson.Tests
{public class JsonSerializationTests{public static MyState GetSampleData(){return new MyState{Id = 11,Name = "CurrentState",IsReady = true,LastUpdated = new DateTime(2015, 10, 21),Model = new MyModel { FirstName = "Alex", LastName = "Brown", BirthDate = new DateTime(1990, 1, 12) }};}[Fact]public void CanDeserializeMyStateTest(){var data = GetSampleData();Assert.Equal(typeof(MyModel), data.Model.GetType());var json = JsonSerializer.Serialize(data);var restoredData = JsonSerializer.Deserialize<MyState>(json);Assert.NotNull(restoredData.Model);Assert.Equal(typeof(MyModel), restoredData.Model.GetType());}}
}

在测试类中,您可以看到为我们创建测试对象的static方法GetSampleData,而在CanDeserializeMyStateTest中,我们使用了该方法,并尝试将测试对象序列化为json并将其反序列化为restoredData变量。然后,我们检查restoredData.Model.GetType()是否是typeof(MyModel),但是如果您运行测试,则此Assert操作将失败。JsonSerializer无法识别Model类型,并在JsonElement其中放置原始json数据。

让我们帮助JsonSerializer并提供Model类型以在另一个测试中反序列化json原始数据:

[Fact]
public void CanDeserializeMyStateWithJsonElementTest()
{var data = GetSampleData();Assert.Equal(typeof(MyModel), data.Model.GetType());var json = JsonSerializer.Serialize(data);var restoredData = JsonSerializer.Deserialize<MyState>(json);Assert.NotNull(restoredData.Model);Assert.Equal(typeof(JsonElement), restoredData.Model.GetType());var modelJsonElement = (JsonElement)restoredData.Model;var modelJson = modelJsonElement.GetRawText();restoredData.Model = JsonSerializer.Deserialize<MyModel>(modelJson);Assert.Equal(typeof(MyModel), restoredData.Model.GetType());
}

如果你运行这个测试,它会通过,因为现在我们读到JsonElement的restoredData.Model和明确的反序列化它:

restoredData.Model = JsonSerializer.Deserialize<MyModel>(modelJson);

因此,当我们知道Model属性对象的类型时,我们可以轻松地从原始json恢复它。

现在有了一个可以工作的原型,我们可以将实现封装在DemoSystemTextJson项目的一个类中,并将Model类型存储在json中。

修改模型方法

存储Model类型的最简单直接的方法是扩展MyState类并向其添加ModelFullName属性。

让我们在DemoSystemTextJson项目中创建IJsonModelWrapper:

using System;
using System.Collections.Generic;
using System.Text;namespace DemoSystemTextJson
{public interface IJsonModelWrapper{string ModelFullName { get; set; }}
}

然后,将MyStateModified类添加到测试项目中以独立测试此方法:

using System;
using System.Collections.Generic;
using System.Text;namespace DemoSystemTextJson.Tests
{public class MyStateModified : IJsonModelWrapper{public int Id { get; set; }public string Name { get; set; }public bool IsReady { get; set; }public DateTime LastUpdated { get; set; }public object Model { get; set; }// IJsonModelWrapperpublic string ModelFullName { get; set; }}
}

MyStateModified包含与MyState类相同的属性,并添加ModelFullName,用于存储反序列化的模型类型。

让我们创建一个将支持ModelFullName属性的填充和使用的JsonModelConverter:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;namespace DemoSystemTextJson
{public class JsonModelConverter{private readonly Dictionary<string, Type> _modelTypes;public JsonModelConverter(){_modelTypes = new Dictionary<string, Type>();}public string Serialize(IJsonModelWrapper source, Type modelType){_modelTypes[modelType.FullName] = modelType;source.ModelFullName = modelType.FullName;var json = JsonSerializer.Serialize(source, source.GetType());return json;}public T Deserialize<T>(string json)where T : class, IJsonModelWrapper, new(){var result = JsonSerializer.Deserialize(json, typeof(T)) as T;var modelName = result.ModelFullName;var objectProperties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.PropertyType == typeof(object));foreach (var property in objectProperties){var model = property.GetValue(result);if (model is JsonElement){var modelJsonElement = (JsonElement)model;var modelJson = modelJsonElement.GetRawText();var restoredModel = JsonSerializer.Deserialize(modelJson, _modelTypes[modelName]);property.SetValue(result, restoredModel);}}return result as T;}}
}

您可以看到该Serialize方法使用Model类型名称填充ModelFullName属性,并且将类型保留在_modelTypes字典中以进行反序列化。

该Deserialize方法是泛型的,它期望结果对象类型作为模板参数。

它从反序列化的对象读取ModelFullName,然后找到类型为object的所有属性,然后使用在_modelTypes字典中找到的显式类型反序列化它们。

让我们用添加到测试项目中的单元测试来测试它:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Xunit;
using Xunit.Abstractions;namespace DemoSystemTextJson.Tests
{public class JsonModelConverterTests{private MyStateModified GetSampleData(){return new MyStateModified{Id = 11,Name = "CurrentState",IsReady = true,LastUpdated = new DateTime(2015, 10, 21),Model = new MyModel { FirstName = "Alex", LastName = "Brown", BirthDate = new DateTime(1990, 1, 12) }};}private readonly ITestOutputHelper _output;public JsonModelConverterTests(ITestOutputHelper output){_output = output;}[Fact]public void JsonModelConverterSerializeTest(){var data = GetSampleData();var converter = new JsonModelConverter();var json = converter.Serialize(data, data.Model.GetType());var restored = converter.Deserialize<MyStateModified>(json);Assert.NotNull(restored.Model);Assert.True(restored.Model.GetType() == typeof(MyModel));}[Fact]public void JsonModelConverterPerformanceTest(){var sw = new Stopwatch();sw.Start();var converter = new JsonModelConverter();for (int i = 0; i < 1000000; i++){var data = GetSampleData();var json = converter.Serialize(data, data.Model.GetType());var restored = converter.Deserialize<MyStateModified>(json);}sw.Stop();_output.WriteLine($"JsonModelConverterPerformanceTest elapsed {sw.ElapsedMilliseconds} ms");}}
}

如果运行JsonModelConverterSerializeTest,则可以看到还原的对象具有正确的Model类型和值。

我添加了另一个测试JsonModelConverterPerformanceTest,该测试执行一百万次序列化和反序列化,并输出此操作所用的时间。

我的机器花了大约7秒钟。

它可以工作并且速度很快,但是让我们尝试另一种我们不想扩展model类的方法。

包装方法

该wrapper是基于MyState的一个单独类,它具有ModelFullName特性,让我们在单元测试项目中创建它:

using System;
using System.Collections.Generic;
using System.Text;namespace DemoSystemTextJson.Tests
{public class MyStateWrapper : MyState, IJsonModelWrapper{public string ModelFullName { get; set; }}
}

JsonWrapperConverter 具有更复杂的实现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;namespace DemoSystemTextJson
{public class JsonWrapperConverter{private readonly Dictionary<Type, Type> _wrapperByTypeDictionary;private readonly Dictionary<string, Type> _modelTypes;public JsonWrapperConverter(){_wrapperByTypeDictionary = new Dictionary<Type, Type>();_modelTypes = new Dictionary<string, Type>();}public void AddModel<M>()where M : class, new(){_modelTypes[typeof(M).FullName] = typeof(M);}public void AddWrapper<W, T>()where W : class, IJsonModelWrapper, new()where T : class, new(){_wrapperByTypeDictionary[typeof(T)] = typeof(W);}public IJsonModelWrapper CreateInstance(object source, Type wrapperType, Type modelType){var json = JsonSerializer.Serialize(source);var wrapper = JsonSerializer.Deserialize(json, wrapperType) as IJsonModelWrapper;wrapper.ModelFullName = modelType.FullName;return wrapper;}public string Serialize(object source, Type modelType){Type wrapperType = _wrapperByTypeDictionary[source.GetType()];var wrapper = CreateInstance(source, wrapperType, modelType);var json = JsonSerializer.Serialize(wrapper, wrapperType);return json;}public T Deserialize<T>(string json)where T : class, new(){Type wrapperType = _wrapperByTypeDictionary[typeof(T)];var result = JsonSerializer.Deserialize(json, wrapperType) as IJsonModelWrapper;var modelName = result.ModelFullName;var objectProperties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.PropertyType == typeof(object));foreach (var property in objectProperties){var model = property.GetValue(result);if (model is JsonElement){var modelJsonElement = (JsonElement)model;var modelJson = modelJsonElement.GetRawText();var restoredModel = JsonSerializer.Deserialize(modelJson, _modelTypes[modelName]);property.SetValue(result, restoredModel);}}return result as T;}}
}

对于每种源对象类型,我们将需要创建一个包装并将它们存储在_wrapperByTypeDictionary字典中和_modelTypes字典中。

AddModel和AddWrapper用于提供源和包装类型并进行存储。

CreateInstance方法被Serialize用于从源对象创建包装对象。包装对象将具有所有源属性和一个属性—— ModelFullName。

Deserialize方法还是泛型的。它通过字典中的源类型找到包装器类型,然后反序列化包装器并读取ModelFullName。然后,它使用反射来读取所有动态属性(typeof(object)),并从原始json恢复它们。

为了测试这一点,我们创建JsonWrapperConverterTests:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Xunit;
using Xunit.Abstractions;namespace DemoSystemTextJson.Tests
{public class JsonWrapperConverterTests{private MyState GetSampleData(){return new MyState{Id = 11,Name = "CurrentState",IsReady = true,LastUpdated = new DateTime(2015, 10, 21),Model = new MyModel { FirstName = "Alex", LastName = "Brown", BirthDate = new DateTime(1990, 1, 12) }};}private readonly ITestOutputHelper _output;public JsonWrapperConverterTests(ITestOutputHelper output){_output = output;}[Fact]public void JsonWrapperConverterSerializeTest(){var data = GetSampleData();var converter = new JsonWrapperConverter();converter.AddWrapper<MyStateWrapper, MyState>();converter.AddModel<MyModel>();var json = converter.Serialize(data, data.Model.GetType());var restored = converter.Deserialize<MyState>(json);Assert.NotNull(restored.Model);Assert.True(restored.Model.GetType() == typeof(MyModel));}[Fact]public void JsonWrapperConverterPerformanceTest(){var sw = new Stopwatch();sw.Start();var converter = new JsonWrapperConverter();converter.AddWrapper<MyStateWrapper, MyState>();converter.AddModel<MyModel>();for (int i = 0; i < 1000000; i++){var data = GetSampleData();var json = converter.Serialize(data, data.Model.GetType());var restored = converter.Deserialize<MyState>(json);}sw.Stop();_output.WriteLine($"JsonWrapperConverterPerformanceTest elapsed {sw.ElapsedMilliseconds} ms");}[Fact]public void JsonNewtonsoftPerformanceTest(){var sw = new Stopwatch();sw.Start();var settings = new Newtonsoft.Json.JsonSerializerSettings{TypeNameAssemblyFormatHandling = Newtonsoft.Json.TypeNameAssemblyFormatHandling.Simple,TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects,};for (int i = 0; i < 1000000; i++){var data = GetSampleData();var json = Newtonsoft.Json.JsonConvert.SerializeObject(data, settings);var restored = Newtonsoft.Json.JsonConvert.DeserializeObject<MyState>(json);}sw.Stop();_output.WriteLine($"JsonNewtonsoftPerformanceTest elapsed {sw.ElapsedMilliseconds} ms");}}
}

如果运行JsonWrapperConverterSerializeTest,您将看到包装方法也有效。

我还添加JsonWrapperConverterPerformanceTest和JsonNewtonsoftPerformanceTest以检查了它们的性能。

如果运行所有性能测试,您将能够看到与我相似的结果:

JsonModelConverterPerformanceTest

5654毫秒

JsonWrapperConverterSerializeTest

9760毫秒

JsonNewtonsoftPerformanceTest

10671毫秒

总结

今天,我们已经表明,如果您需要将项目从Newtonsoft迁移到System.Text.Json序列化器,则会遇到一些困难,因为System.Text.Json序列化器不支持使用该“$type”属性进行动态对象反序列化。我们实现了两种方法来对注入ModelFullName属性的动态对象进行序列化和反序列化,以按Model类型进行显式反序列化。

如果可以修改model类并添加ModelFullName属性,则可以使用最快和最简单的序列化,但是如果不能更改model类,则可以使用比Newtonsoft序列化还快的包装方法。

将$type添加到System.Text.Json序列化中,就像Newtonsoft那样用于动态对象属性相关推荐

  1. .NET 6新特性试用 | System.Text.Json序列化代码自动生成

    前言 几乎所有.NET序列化程序的实现基础都是反射.下列代码是Newtonsoft.Json的实现: protected virtual JsonProperty CreateProperty(Mem ...

  2. System.Text.Json中时间格式化

    转自:Rayom cnblogs.com/Rayom/p/13967415.html 简介 .Net Core 3.0开始全新推出了一个名为System.Text.Json的Json解析库,用于序列化 ...

  3. [译]试用新的System.Text.Json API

    译注 尝试新的System.Text.Json API 对于.NET Core 3.0,我们 提供了一个名为System.Text.Json的全新命名空间 ,支持读取器/写入器,文档对象模型(DOM) ...

  4. 使用.Net6中的System.Text.Json遇到几个常见问题及解决方案

    前言 以前.NetCore是不内置JSON库的,所以大家都用Newtonsoft的JSON库,而且也确实挺好用的,不过既然官方出了标准库,那更方便更值得我们多用用,至少不用每次都nuget安装Newt ...

  5. System.Text.Json 中的字符编码

    System.Text.Json 中的字符编码 Intro 默认的 System.Text.Json 序列化的时候会把所有的非 ASCII 的字符进行转义,这就会导致很多时候我们的一些非 ASCII ...

  6. .NET Core 3.0 System.Text.Json 和 Newtonsoft.Json 行为不一致问题及解决办法

    行为不一致 .NET Core 3.0 新出了个内置的 JSON 库, 全名叫做尼古拉斯 System.Text.Json - 性能更高占用内存更少这都不是事... 对我来说, 很多或大或小的项目能少 ...

  7. System.Text.Json 自定义 Conveter

    System.Text.Json 自定义 Conveter Intro System.Text.Json 作为现在 .NET 默认提供的高性能 JSON 序列化器,对于一些比较特殊类型支持的并不太好, ...

  8. Teams Bot 如何使用新的 System.Text.Json 库

    我最近把 LuckyDraw的代码升级到了 .net core 3.1,当然我也很想使用最新的微软json库,System.Text.Json这个库的性能比之前Newtonsoft.Json速度更快, ...

  9. 接口返回json对象出现套娃递归问题 | System.Text.Json 版本

    前言 看到一篇文章<Asp-Net-Core开发笔记:接口返回json对象出现套娃递归问题> 原文是使用 NewtonsoftJson 解决的返回json对象出现套娃递归问题: servi ...

最新文章

  1. Ubuntu系统创建AndroidStudio启动图标(快捷方式)
  2. Unity Log重新定向
  3. java B2B2C 源码 多级分销Springcloud多租户电子商城系统- 整合企业架构的技术点(二)...
  4. Linux_LVM、RAID_RHEL7
  5. Linux重定向指令
  6. JUC锁-CyclicBarrier(七)
  7. 徐涛八套卷pdf_徐涛八套卷pdf,11月9日资料更新!
  8. AUTOSAR从入门到精通100讲(三十一)-AutoSar中RTE的生成准备
  9. 宝藏新品牌成长白皮书:新品牌心智与营销增长方法论
  10. 计算机主板 也叫系统版,电脑主板是什么
  11. 模式识别和机器学习的区别
  12. 周立功烧写器(ZLG SMARTPRO5000U-Plus)烧写验证
  13. armbian编译安装mentohust 认证锐捷客户端
  14. Android视频播放器
  15. 腾讯云服务器架设mir2
  16. android+特殊符号过滤,android 特殊符号过滤
  17. MySQL数据库W版与L版转换_mysql博文总结
  18. 招聘 | 腾讯TEG机器学习平台预训练组实习生JD
  19. 开源扫描仪的工具箱:安全行业从业人员自研开源扫描器合集
  20. 需要近期熟悉的表_2019_3_18

热门文章

  1. EMC测试仪器_电巢学堂:单片机系统EMC测试和故障排除
  2. 分式混合运算20道题_FAG剖分式调心滚子轴承的性能
  3. 手绘水彩卡通插画 | 艺术品因有灵魂而珍藏
  4. a6gpp php,内行人才知道的古董级玛莎拉蒂A6G 2000
  5. token 微信access 过期_如何设计 QQ、微信等第三方账号登陆 ?以及设计数据库表!...
  6. 正版python怎么下载_怎么下载官网python并安装
  7. Linux 下Shell脚本中的加减乘除运算
  8. NMI watchdog: BUG: soft lockup - CPU#2 stuck for 23s!
  9. Cilium:BPF和XDP参考指南
  10. 大工13秋 c/c++语言程序设计 在线作业3,大工19秋《JavaScript基础教程与应用》在线作业3【满分答案】...