扩展字典的属性不会在序列化中显示
Properties of extended dictionary won't show in serialization
我扩展了一个字典(它是翻译的完美数据结构)并添加了一个标记来告诉将执行哪种翻译。
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",
}
我可以通过在我的字典中添加一个项目来解决它,其中 key 是 name 和 value当然是驴子。但那个务实的解决方案,我更愿意保存为我的后备。目前我有一些额外的时间,想尝试一下这个结构。另外,我可以想象 name 可能会变成 int 而不是 string 或者甚至更多复杂的结构来描述例如时间戳什么的。那将完全破坏字典的契约(字符串到字符串的映射)。
这似乎是设计意图——与 Newtonsoft、JavaScriptSerializer
和 DataContractJsonSerializer
一样,字典键和值是序列化的,而不是常规属性。
作为扩展 Dictionary<TKey, TValue>
的替代方法,您可以通过将字典封装在容器 class 中并用 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);
请注意 docs
中的此限制
The dictionary's TKey value must be String, and TValue must be JsonElement or Object.
(顺便说一句,类似的方法适用于拥有自己的 JsonExtensionDataAttribute
的 Newtonsoft。如果您同时使用这两个库,请确保不要混淆属性。)
演示 fiddle #1 here.
如果对您的数据模型进行这种修改不方便,您可以引入一个自定义 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 比直接使用 Utf8JsonReader
和 Utf8JsonWriter
更简单,因为边缘情况和命名策略会自动处理。只有当性能很重要时,我才会直接与 reader 和作家一起工作。
无论采用哪种方法,都需要 JsonNamingPolicy.CamelCase
将 JSON 中的 "name"
绑定到模型中的 Name
。
演示 fiddle #2 here.
我扩展了一个字典(它是翻译的完美数据结构)并添加了一个标记来告诉将执行哪种翻译。
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",
}
我可以通过在我的字典中添加一个项目来解决它,其中 key 是 name 和 value当然是驴子。但那个务实的解决方案,我更愿意保存为我的后备。目前我有一些额外的时间,想尝试一下这个结构。另外,我可以想象 name 可能会变成 int 而不是 string 或者甚至更多复杂的结构来描述例如时间戳什么的。那将完全破坏字典的契约(字符串到字符串的映射)。
这似乎是设计意图——与 Newtonsoft、JavaScriptSerializer
和 DataContractJsonSerializer
一样,字典键和值是序列化的,而不是常规属性。
作为扩展 Dictionary<TKey, TValue>
的替代方法,您可以通过将字典封装在容器 class 中并用 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);
请注意 docs
中的此限制The dictionary's TKey value must be String, and TValue must be JsonElement or Object.
(顺便说一句,类似的方法适用于拥有自己的 JsonExtensionDataAttribute
的 Newtonsoft。如果您同时使用这两个库,请确保不要混淆属性。)
演示 fiddle #1 here.
如果对您的数据模型进行这种修改不方便,您可以引入一个自定义 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 比直接使用 Utf8JsonReader
和 Utf8JsonWriter
更简单,因为边缘情况和命名策略会自动处理。只有当性能很重要时,我才会直接与 reader 和作家一起工作。
无论采用哪种方法,都需要 JsonNamingPolicy.CamelCase
将 JSON 中的 "name"
绑定到模型中的 Name
。
演示 fiddle #2 here.