首页 > 解决方案 > 扩展字典的属性不会在序列化中显示

问题描述

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

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

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

string output = JsonSerializer.Serialize(source);

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

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

我期待下面表格上的 JSON。

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

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

标签: c#serializationdeserialization.net-core-3.0system.text.json

解决方案


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

作为扩展的替代方法Dictionary<TKey, TValue>,您可以通过将字典封装在容器类中并使用以下标记来获取所需的 JSON 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 必须是JsonElementObject

(顺便说一句,类似的方法适用于拥有自己的 .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都需要"name"在 JSON 中绑定到Name模型中。

演示小提琴#2在这里


推荐阅读