扩展字典的属性不会在序列化中显示



我扩展了一个字典(这是翻译的完美数据结构(,并添加了一个标记,告诉将执行哪种翻译。

internal class Translation : Dictionary<string, string>
{
public string Name { get; set; }
}

但是,当我序列化对象时,我只在输出字符串中获取键值对。名称不显示。我想使用叔叔Microsoft的礼品袋中的东西,即System.Text.Json,所以我做了以下事情。

string output = JsonSerializer.Serialize(source);

我怀疑我需要实现一个自定义序列化程序,但对于这个简单的情况来说,这太忙了。我的经验告诉我,工具中捆绑了一种整洁、流畅的方法(我根本不知道(。

怎么办?或者,如果不可能顺利,为什么这是一个复杂的问题(我显然没有意识到(?

我期待下面的表格上的 JSON。

{
"name": "donkey",
"key1": "value1",
"key2": "value2",
"key3": "value3",
}

当然,我可以通过向字典中添加一个项目来解决它,名称。但那个务实的解决方案,我宁愿保存作为我的后备。目前我有一些额外的时间,想玩一下结构。此外,我可以想象名称可能会变成一个 int而不是字符串,甚至可能是一个更复杂的结构来描述,例如时间戳或其他东西。这将完全破坏字典的契约(字符串到字符串映射(。

这似乎是设计意图 - 与Newtonsoft,JavaScriptSerializerDataContractJsonSerializer一样,字典键和值是序列化的,而不是常规属性。

作为扩展Dictionary<TKey, TValue>的替代方法,您可以通过将字典封装在容器类中并用JsonExtensionDataAttribute标记字典来获取所需的 JSON:

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 或 Object。

(顺便说一句,类似的方法也适用于Newtonsoft,它有自己的JsonExtensionDataAttribute。 如果同时使用这两个库,请确保不要混淆属性。

演示小提琴#1在这里。

如果对数据模型的这种修改不方便,您可以引入一个自定义JsonConverter<Translation>,该(反(序列化 DTO,如上面的模型,然后将 DTO 映射到最终模型:

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 比直接使用Utf8JsonReaderUtf8JsonWriter更简单,因为边缘情况和命名策略会自动处理。 只有当性能至关重要时,我才会直接与读者和作者合作。

无论使用哪种方法JsonNamingPolicy.CamelCase都需要将 JSON 中的"name"绑定到模型中的Name

演示小提琴#2在这里。

最新更新