如何用Newtonsoft.Json正确序列化/反序列化HashSet<string>为键的Dictionary
问题原因
JSON 规范要求对象的键必须为字符串类型,Newtonsoft.Json 默认处理复杂类型作为字典键的场景时,仅会调用键的 ToString() 方法输出类名,对于 HashSet<string> 类型的键只会得到 System.Collections.Generic.HashSet1[System.String]这类无实际意义的字符串,丢失了集合内部的元素内容,因此反序列化时无法还原成原始的HashSet实例。 另外你代码中使用的[JsonPropertyName]是 System.Text.Json 库的特性,Newtonsoft.Json 中对应的特性是[JsonProperty]`,否则序列化时自定义的属性别名不会生效。
解决方案1:自定义JsonConverter(推荐)
通过自定义 JsonConverter 完全控制该字典类型的序列化、反序列化逻辑,对业务代码侵入性最低。
第一步:实现自定义转换器
using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; public class HashSetKeyDictionaryConverter : JsonConverter<Dictionary<HashSet<string>, string>> { public override void WriteJson(JsonWriter writer, Dictionary<HashSet<string>, string> value, JsonSerializer serializer) { writer.WriteStartObject(); foreach (var kvp in value) { // 对HashSet元素排序后序列化,保证相同元素、不同顺序的HashSet生成相同的键,符合HashSet的相等判定逻辑 var sortedKeyElements = kvp.Key.OrderBy(x => x); var serializedKey = JsonConvert.SerializeObject(sortedKeyElements); writer.WritePropertyName(serializedKey); writer.WriteValue(kvp.Value); } writer.WriteEndObject(); } public override Dictionary<HashSet<string>, string> ReadJson(JsonReader reader, Type objectType, Dictionary<HashSet<string>, string> existingValue, bool hasExistingValue, JsonSerializer serializer) { var result = new Dictionary<HashSet<string>, string>(); if (reader.TokenType == JsonToken.Null) return result; var jsonObj = JObject.Load(reader); foreach (var prop in jsonObj.Properties()) { var keySet = JsonConvert.DeserializeObject<HashSet<string>>(prop.Name); result[keySet] = prop.Value.Value<string>(); } return result; } }
第二步:给目标属性标记转换器
调整 SampleClass 的属性定义,添加转换器标记并修正特性名:
public class SampleClass { [JsonProperty("DimensionValueDictionary")] public Dictionary<string, List<string>> DimensionValueDictionary { get; set; } [JsonProperty("DimensionValueContentMappings")] [JsonConverter(typeof(HashSetKeyDictionaryConverter))] public Dictionary<HashSet<string>, string> DimensionValueMappings { get; set; } public override string ToString() { return Newtonsoft.Json.JsonConvert.SerializeObject(this); } }
调整完成后原有的调用代码无需修改即可正常完成序列化、反序列化。
解决方案2:中间属性适配(无需自定义转换器)
如果不想编写自定义转换器,可以新增一个用于序列化的中间存储属性,业务侧仍使用原有类型的属性访问:
public class SampleClass { [JsonProperty("DimensionValueDictionary")] public Dictionary<string, List<string>> DimensionValueDictionary { get; set; } // 序列化用的存储属性,对外不可见 [JsonProperty("DimensionValueContentMappings")] private Dictionary<string, string> SerializedMappings { get; set; } // 业务侧使用的属性,不参与序列化 [JsonIgnore] public Dictionary<HashSet<string>, string> DimensionValueMappings { get { return SerializedMappings?.ToDictionary( kvp => JsonConvert.DeserializeObject<HashSet<string>>(kvp.Key), kvp => kvp.Value ); } set { SerializedMappings = value?.ToDictionary( kvp => JsonConvert.SerializeObject(kvp.Key.OrderBy(x => x)), kvp => kvp.Value ); } } public override string ToString() { return Newtonsoft.Json.JsonConvert.SerializeObject(this); } }
注意事项
如果你的业务逻辑中要求元素完全相同但插入顺序不同的 HashSet 视为同一个键,序列化前必须对 HashSet 的元素做排序处理,否则序列化生成的键会因为顺序不同而被判定为不同的键。
内容的提问来源于stack exchange,提问作者hexcode
相关产品推荐
相关产品推荐

