如何将含Dictionary属性的文档插入Cosmos DB且不生成Key/Value关键字
根因说明
JSON规范仅支持字符串类型作为对象的键,当.NET序列化器处理键为int等非字符串类型的Dictionary时,默认会将其序列化为包含Key、Value字段的数组结构,就是你现在看到的存储结果。
可行实现方案
方案1:自定义序列化转换器(最推荐,无侵入性)
你可以针对Dictionary<int, T>类型写自定义JsonConverter,直接控制序列化逻辑,不需要修改原有业务代码结构,兼容Newtonsoft.Json和System.Text.Json两种序列化组件:
若你使用Newtonsoft.Json作为Cosmos序列化器
自定义转换器代码:
public class IntKeyDictionaryConverter<TValue> : JsonConverter<Dictionary<int, TValue>> { public override void WriteJson(JsonWriter writer, Dictionary<int, TValue> value, JsonSerializer serializer) { writer.WriteStartObject(); foreach (var pair in value) { writer.WritePropertyName(pair.Key.ToString()); serializer.Serialize(writer, pair.Value); } writer.WriteEndObject(); } public override Dictionary<int, TValue> ReadJson(JsonReader reader, Type objectType, Dictionary<int, TValue> existingValue, bool hasExistingValue, JsonSerializer serializer) { var dict = new Dictionary<int, TValue>(); var jObj = JObject.Load(reader); foreach (var prop in jObj.Properties()) { dict[int.Parse(prop.Name)] = prop.Value.ToObject<TValue>(serializer); } return dict; } }
之后只要在你的类属性上加特性即可生效:
public class Foo { [JsonConverter(typeof(IntKeyDictionaryConverter<int>))] public Dictionary<int, int> dictionary { get; } }
若你使用System.Text.Json作为Cosmos序列化器
自定义转换器逻辑和上面一致,只是继承JsonConverter<Dictionary<int, TValue>>实现对应的读写方法,之后可以选择给属性加[JsonConverter]特性,或者在创建CosmosClient时将转换器注册到全局序列化配置中,对所有Dictionary<int, T>生效。
方案2:提前转换字典键类型(简单易实现)
直接将属性定义修改为键为字符串的Dictionary,赋值时将int键转为字符串存入:
public class Foo { public Dictionary<string, int> dictionary { get; } }
序列化器遇到字符串键的Dictionary会直接生成你期望的对象结构,读取时如果需要用int键,只要调用int.Parse()转换即可,性能损耗几乎可以忽略。
方案3:手动构造匿名对象(临时解决方案)
如果不想改序列化配置,也可以在插入前将Dictionary转为字符串键的字典:
var formattedDict = foo.dictionary.ToDictionary(pair => pair.Key.ToString(), pair => pair.Value);
之后把这个新的Dictionary<string, int>赋值给要插入的对象属性即可。
你之前尝试ExpandoObject没有生效是因为默认序列化器仍然会把实现了IDictionary<int, T>的对象按键值对数组处理,只要你手动把int键转成字符串再存到ExpandoObject中也能实现效果,但是没有上面的方案高效。
内容的提问来源于stack exchange,提问作者Aminie

