JSON Serialization/Contract Resolvers:将内嵌键值对列表转为同级键值对
实现方案
针对你需要将CustomFields中的键值对与固定属性(如part、quant)平级序列化的需求,以下是几种最优实现方式:
一、Json.NET(Newtonsoft.Json)自定义ContractResolver(推荐)
这种方式无需修改原有类结构,直接在序列化阶段动态展开CustomFields,同时支持反序列化,适合长期使用。
1. 原有类定义
public class OrderItem { public string Part { get; set; } public int Quant { get; set; } public Dictionary<string, object> CustomFields { get; set; } = new Dictionary<string, object>(); }
2. 自定义ContractResolver和ValueProvider
public class CustomFieldsResolver : DefaultContractResolver { protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { var properties = base.CreateProperties(type, memberSerialization); var customFieldsProp = properties.FirstOrDefault(p => p.PropertyName == nameof(OrderItem.CustomFields)); if (customFieldsProp != null) { // 移除原CustomFields属性 properties.Remove(customFieldsProp); // 动态添加CustomFields中的键作为序列化属性 var dummyInstance = Activator.CreateInstance(type); var customFields = dummyInstance.GetType().GetProperty(nameof(OrderItem.CustomFields)) .GetValue(dummyInstance) as Dictionary<string, object>; if (customFields != null) { foreach (var kvp in customFields) { properties.Add(new JsonProperty { PropertyName = kvp.Key, Readable = true, Writable = true, ValueProvider = new CustomFieldValueProvider(kvp.Key) }); } } } return properties; } } public class CustomFieldValueProvider : IValueProvider { private readonly string _key; public CustomFieldValueProvider(string key) => _key = key; public object GetValue(object target) { var customFields = target.GetType().GetProperty(nameof(OrderItem.CustomFields)) .GetValue(target) as Dictionary<string, object>; customFields.TryGetValue(_key, out var value); return value; } public void SetValue(object target, object value) { var customFields = target.GetType().GetProperty(nameof(OrderItem.CustomFields)) .GetValue(target) as Dictionary<string, object>; customFields[_key] = value; } }
3. 使用示例
var item = new OrderItem { Part = "A123", Quant = 5, CustomFields = new Dictionary<string, object> { ["Color"] = "Red", ["Size"] = "XL" } }; var settings = new JsonSerializerSettings { ContractResolver = new CustomFieldsResolver(), Formatting = Formatting.Indented }; var json = JsonConvert.SerializeObject(item, settings);
输出结果
{ "Part": "A123", "Quant": 5, "Color": "Red", "Size": "XL" }
二、System.Text.Json 自定义JsonConverter
如果你使用的是.NET Core/.NET 5+自带的System.Text.Json,可通过自定义转换器实现同样效果:
转换器代码
public class OrderItemConverter : JsonConverter<OrderItem> { public override OrderItem Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var item = new OrderItem(); using var doc = JsonDocument.ParseValue(ref reader); var root = doc.RootElement; // 读取固定属性 if (root.TryGetProperty(nameof(OrderItem.Part), out var partProp)) item.Part = partProp.GetString(); if (root.TryGetProperty(nameof(OrderItem.Quant), out var quantProp)) item.Quant = quantProp.GetInt32(); // 读取剩余属性到CustomFields foreach (var prop in root.EnumerateObject()) { if (prop.Name != nameof(OrderItem.Part) && prop.Name != nameof(OrderItem.Quant)) { item.CustomFields[prop.Name] = JsonSerializer.Deserialize<object>(prop.Value.GetRawText(), options); } } return item; } public override void Write(Utf8JsonWriter writer, OrderItem value, JsonSerializerOptions options) { writer.WriteStartObject(); // 写入固定属性 writer.WriteString(nameof(OrderItem.Part), value.Part); writer.WriteNumber(nameof(OrderItem.Quant), value.Quant); // 写入CustomFields中的键值对 foreach (var kvp in value.CustomFields) { writer.WritePropertyName(kvp.Key); JsonSerializer.Serialize(writer, kvp.Value, options); } writer.WriteEndObject(); } }
使用示例
var options = new JsonSerializerOptions { Converters = { new OrderItemConverter() }, WriteIndented = true }; var json = JsonSerializer.Serialize(item, options);
三、ExpandoObject 临时转换(适合一次性场景)
如果只是临时序列化需求,无需支持反序列化,可将对象转换为ExpandoObject后再序列化:
public static ExpandoObject ConvertToExpando(OrderItem item) { var expando = new ExpandoObject(); var expandoDict = (IDictionary<string, object>)expando; // 复制固定属性 var props = item.GetType().GetProperties().Where(p => p.Name != nameof(OrderItem.CustomFields)); foreach (var prop in props) { expandoDict[prop.Name] = prop.GetValue(item); } // 添加CustomFields中的键值对 foreach (var kvp in item.CustomFields) { expandoDict[kvp.Key] = kvp.Value; } return expando; }
使用示例
var expandoItem = ConvertToExpando(item); var json = JsonConvert.SerializeObject(expandoItem, Formatting.Indented);
方案对比
- ContractResolver/JsonConverter:最优长期方案,无需修改业务类结构,支持正反序列化,集成度高。
- ExpandoObject:代码量少,适合临时序列化场景,但反序列化需要额外处理,不适合复杂业务流程。
内容的提问来源于stack exchange,提问作者Martian99
相关产品推荐
相关产品推荐

