扩展字典的属性不会在序列化中显示 [英] Properties of extended dictionary won't show in serialization

查看:51
本文介绍了扩展字典的属性不会在序列化中显示的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我扩展了一个字典(它是完美的翻译数据结构)并添加了一个标记来说明将执行哪种翻译.

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",
}

我可以通过在我的字典中添加一个条目来解决它,keynamevaluedonkey, 当然.但是这个务实的解决方案,我更喜欢保存作为我的后备.目前我有一些额外的时间,想玩转这个结构.另外,我可以想象 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,JavaScriptSerializerDataContractJsonSerializer,字典键和值被序列化,而不是常规属性.

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

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆