You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.18 20:06:06