using System.IO; using System.Text.Json; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using JsonSerializer = System.Text.Json.JsonSerializer; using Xunit; using Yh.Platform.Abstractions; namespace Yh.Platform.Abstractions.Tests; public class ModelSerializationTests { private static JObject LoadFixtureBareData() { var path = Path.Combine(AppContext.BaseDirectory, "fixtures", "envelope-success.json"); return JObject.Parse(File.ReadAllText(path))["bareData"]!.Value()!; } private static JObject LoadFixtureWrapped() { var path = Path.Combine(AppContext.BaseDirectory, "fixtures", "envelope-success.json"); return JObject.Parse(File.ReadAllText(path))["wrappedData"]!.Value()!; } [Fact] public void Newtonsoft_反序列化fixture_含currentRole嵌套() { var user = LoadFixtureBareData().ToObject(new Newtonsoft.Json.JsonSerializer { // net5 消费方形态:ASP.NET Core AddNewtonsoftJson 默认大小写不敏感、保留属性名 ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(), }); Assert.Equal(1001, user!.Id); Assert.Equal("zhangsan", user.Account); Assert.Equal("张三", user.Name); Assert.Equal(2, user.School!.Id); Assert.Equal("云海第一中学", user.School.Name); Assert.Equal(3, user.CurrentRole!.RoleId); Assert.Equal("教师", user.CurrentRole.RoleName); Assert.Equal("云海第一中学", user.CurrentRole.SchName); Assert.Equal(21, user.CurrentRole.ChildId); Assert.Equal(2, user.Schools!.Count); Assert.Equal(new List { 3, 5 }, user.RoleIds); Assert.Equal(3, user.Groups!.Count); } [Fact] public void Newtonsoft_默认设置_大小写不敏感匹配camelCase() { var user = LoadFixtureBareData().ToObject(); Assert.Equal("zhangsan", user!.Account); Assert.Equal("教师", user.CurrentRole!.RoleName); } [Fact] public void SystemTextJson_Web序列化_与fixture逐节点一致() { var user = LoadFixtureBareData().ToObject(); var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { // 对齐 Java 侧 NON_NULL 语义 DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, }; var json = JsonSerializer.Serialize(user, options); // 语义等价:键序无关、null 节点差异容忍(fixture 中显式 null 与省略 null 视为一致) Assert.True(JToken.DeepEquals(JObject.Parse(json), StripNulls(LoadFixtureBareData())), "STJ 序列化结果应与 fixture 语义一致\n" + json); } private static JObject StripNulls(JObject obj) { var clone = (JObject)obj.DeepClone(); var toRemove = new List(); CollectNullProps(clone, toRemove); foreach (var p in toRemove) { p.Remove(); } return clone; } private static void CollectNullProps(JToken token, List collected) { foreach (var child in token.Children()) { if (child is JProperty p) { if (p.Value.Type == JTokenType.Null) { collected.Add(p); continue; } CollectNullProps(p.Value, collected); } else if (child.Type is JTokenType.Object or JTokenType.Array) { CollectNullProps(child, collected); } } } [Fact] public void SystemTextJson_反序列化信封包裹成功样例() { var wrapped = LoadFixtureWrapped(); var envelope = wrapped.ToObject>(new Newtonsoft.Json.JsonSerializer { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(), }); Assert.True(envelope!.IsSuccess); Assert.Equal(0, envelope.Code); Assert.Equal(1001, envelope.Data!.Id); } [Fact] public void BoaApp_Models_双序列化往返() { var app = new BoaApp { Id = 7, Name = "作业提交", Clients = (int)ClientEnum.PcBrowser | (int)ClientEnum.MobileApp, IsPublic = false, BuiltInTag = 0 }; var stj = JsonSerializer.Deserialize(JsonSerializer.Serialize(app, new JsonSerializerOptions(JsonSerializerDefaults.Web)), new JsonSerializerOptions(JsonSerializerDefaults.Web)); var nf = JObject.Parse(JsonConvert.SerializeObject(app, new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() })).ToObject(); Assert.Equal(app.Name, stj!.Name); Assert.Equal(app.Clients, stj.Clients); Assert.Equal(app.Name, nf!.Name); Assert.Equal(false, nf.IsPublic); } }