扩展字典的属性不会在序列化中显示 [英] Properties of extended dictionary won't show in serialization
问题描述
我扩展了一个字典(它是完美的翻译数据结构)并添加了一个标记来说明将执行哪种翻译.
I extended a dictionary (which is perfect data structure for translations) and added a marker telling what kind of translation will be performed.
internal class Translation : Dictionary<string, string>
{
public string Name { get; set; }
}
但是,当我序列化对象时,我只能在输出字符串中获取键值对.名字不显示.我想使用微软叔叔的礼物包中的东西,即 System.Text.Json,所以我执行以下操作.
However, when I serialize the object, I only get the key-value pairs in my output string. The name doesn't show. I wanted to use the stuff from the goodie bag from uncle Microsoft, i.e. System.Text.Json, so I do the following.
string output = JsonSerializer.Serialize(source);
我怀疑我需要实现一个自定义序列化程序,但这对于这个简单的案例来说太过分了.我的经验告诉我,工具中捆绑了一种简洁、流畅的方法(我根本不知道).
My suspicion is that I will need to implement a custom serializer but that's way too much hustle for this simple case. My experience tells me there's a neat, smooth approach bundled in the tools (one that I'm simply not aware of).
怎么做?或者,如果不可能顺利,为什么这是一件复杂的事情(我显然没有意识到)?
How to do it? Alternatively, if not possible smoothly, why is it a complex matter (that I'm apparently failing to appreciate)?
我期待以下表单中的 JSON.
I was expecting a JSON on form below.
{
"name": "donkey",
"key1": "value1",
"key2": "value2",
"key3": "value3",
}
我可以通过在我的字典中添加一个条目来解决它,key 是 name,value 是 donkey, 当然.但是这个务实的解决方案,我更喜欢保存作为我的后备.目前我有一些额外的时间,想玩转这个结构.另外,我可以想象 name 可能会变成 int 而不是 string 或者甚至可能是一个更复杂的结构来描述,例如时间戳什么的.这将完全破坏字典的约定(字符串到字符串的映射).
I can resolve it by adding an item to my dictionary with key being name and value being donkey, of course. But that pragmatic solution, I prefer to save as my fall-back. At the moment I have some extra time and want to play around with the structure. Also, I can imagine that the name might become an int instead of string or maybe even a more complex structure to describe e.g. timestamp or something. That would totally break the contract of the dictionary (being string-to-string mapping).
推荐答案
这似乎是设计意图——就像 Newtonsoft,JavaScriptSerializer
和 DataContractJsonSerializer
,字典键和值被序列化,而不是常规属性.
This seems to be the design intent -- as with Newtonsoft, JavaScriptSerializer
and DataContractJsonSerializer
, the dictionary keys and values are serialized, not the regular properties.
作为扩展Dictionary
的替代方法,您可以通过在容器类中封装字典并使用JsonExtensionDataAttribute
:
As an alternative to extending Dictionary<TKey, TValue>
, you can get the JSON you want by encapsulating a dictionary in a container class and marking the dictionary with JsonExtensionDataAttribute
:
internal class Translation
{
public string Name { get; set; }
[JsonExtensionData]
public Dictionary<string, object> Data { get; set; } = new Dictionary<string, object>();
}
然后序列化如下:
var translation = new Translation
{
Name = "donkey",
Data =
{
{"key1", "value1"},
{"key2", "value2"},
{"key3", "value3"},
},
};
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
// Other options as required
WriteIndented = true,
};
var json = JsonSerializer.Serialize(translation, options);
请注意 文档
字典的 TKey 值必须是 String,并且 TValue 必须是 JsonElement 或 对象.
The dictionary's TKey value must be String, and TValue must be JsonElement or Object.
(顺便说一句,类似的方法适用于 Newtonsoft,它有自己的 JsonExtensionDataAttribute
.如果您同时使用这两个库,请确保不要混淆属性.)
(As an aside, a similar approach would work with Newtonsoft which has its own JsonExtensionDataAttribute
. If you are using both libraries, be sure not to get the attributes confused.)
演示小提琴 #1 此处.
如果对您的数据模型的这种修改不方便,您可以引入自定义JsonConverter<Translation>
像上面的模型那样(反)序列化 DTO,然后将 DTO 映射到您的最终模型:
If this modification to your data model is not convenient, you can introduce a custom JsonConverter<Translation>
that (de)serializes a DTO like the model above, then maps the DTO from and to your final model:
internal class Translation : Dictionary<string, string>
{
public string Name { get; set; }
}
internal class TranslationConverter : JsonConverter<Translation>
{
internal class TranslationDTO
{
public string Name { get; set; }
[JsonExtensionData]
public Dictionary<string, object> Data { get; set; }
}
public override Translation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var dto = JsonSerializer.Deserialize<TranslationDTO>(ref reader, options);
if (dto == null)
return null;
var translation = new Translation { Name = dto.Name };
foreach (var p in dto.Data)
translation.Add(p.Key, p.Value?.ToString());
return translation;
}
public override void Write(Utf8JsonWriter writer, Translation value, JsonSerializerOptions options)
{
var dto = new TranslationDTO { Name = value.Name, Data = value.ToDictionary(p => p.Key, p => (object)p.Value) };
JsonSerializer.Serialize(writer, dto, options);
}
}
然后序列化如下:
var translation = new Translation
{
Name = "donkey",
["key1"] = "value2",
["key2"] = "value2",
["key3"] = "value3",
};
var options = new JsonSerializerOptions
{
Converters = { new TranslationConverter() },
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
// Other options as required
WriteIndented = true,
};
var json = JsonSerializer.Serialize(translation, options);
我发现将(反)序列化为 DTO 比直接使用 Utf8JsonReader
和 Utf8JsonWriter
更简单,因为边缘情况和命名策略会自动处理.只有在性能至关重要时,我才会直接与读者和作者合作.
I find it simpler to (de)serialize to a DTO rather than to work directly with Utf8JsonReader
and Utf8JsonWriter
as edge cases and naming policies get handled automatically. Only if performance is critical will I work directly with the reader and writer.
使用任一方法 JsonNamingPolicy.CamelCase
需要将 JSON 中的 "name"
绑定到模型中的 Name
.
With either approach JsonNamingPolicy.CamelCase
is required to bind "name"
in the JSON to Name
in the model.
演示小提琴 #2 此处.
这篇关于扩展字典的属性不会在序列化中显示的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!