简体   繁体   English

将单个 KeyValuePair<> 序列化为 JSON 作为父 object 的一部分

[英]Serialize single KeyValuePair<> to JSON as part of a parent object

I have a DTO class like this:我有一个像这样的 DTO class:

public class MyDto
{
    public KeyValuePair<string, string> Identifier { get; set; }

    public double Value1 { get; set; }

    public string Value2 { get; set; }
}

And two instances could look like this:两个实例可能如下所示:

var results = new List<MyDto> 
{
    new MyDto
    {
        Identifier = new KeyValuePair<string, string>("Car", "Ford"),
        Value1 = 13,
        Value2 = "A"
    },
    new MyDto 
    {
        Identifier = new KeyValuePair<string, string>("Train", "Bombardier"),
        Value1 = 14,
        Value2 = "B"
    },
};

When serializing the results within ASP.NET Web API (which uses Json.NET), the output looks like this:在 ASP.NET Web API(使用 Json.NET)中序列化结果时,Z78E62Z61F6393D19356

[
  {
    "Identifier": {
      "Key": "Car",
      "Value": "Ford"
    },
    "Value1": 13,
    "Value2": "A"
  },
  {
    "Identifier": {
      "Key": "Train",
      "Value": "Bombardier"
    },
    "Value1": 14,
    "Value2": "B"
  }
]

But I'd like to have it that way:但我想这样:

[
  {
    "Car": "Ford",
    "Value1": 13,
    "Value2": "A"
  },
  {
    "Train": "Bombardier",
    "Value1": 14,
    "Value2": "B"
  }
]

How can I achieve this?我怎样才能做到这一点? Do I have to write a custom JsonConverter and do everything by hand?我是否必须编写自定义JsonConverter并手动完成所有操作? Since the DTO class is located in a separate assembly not having access to Json.NET, the usage of specific Json.NET attributes is not possible.由于 DTO class 位于无法访问 Json.NET 的单独组件中,因此无法使用特定的 Json.NET 属性。

I'm not bound to using KeyValuePair<string, string> .我不一定要使用KeyValuePair<string, string> I only need a data structure that allows me to have a flexible attribute name.我只需要一个允许我拥有灵活属性名称的数据结构。

Edit: this answer applies to the question as originally asked ;编辑:此答案适用于最初提出的问题; it has subsequently been edited, and may now be less useful它随后被编辑,现在可能不太有用


You may need to use a dictionary that has a single element, ie Dictionary<string,string> , which does serialize in the way you want;您可能需要使用具有单个元素的字典,即Dictionary<string,string> ,它以您想要的方式进行序列化; for example:例如:

var obj = new Dictionary<string, string> { { "Car", "Ford" } };
var json = JsonConvert.SerializeObject(obj);
System.Console.WriteLine(json);

Instead of a KeyValuePair<> , you can easily serialize a dictionary as part of a parent object by applying [JsonExtensionData] like so:而不是KeyValuePair<> ,您可以通过应用[JsonExtensionData]轻松地将字典序列化为父 object 的一部分,如下所示:

public class MyDto
{
    [JsonExtensionData]
    public Dictionary<string, object> Identifier { get; set; }

However, you have stated that the usage of specific Json.NET attributes is not possible.但是,您已声明无法使用特定的 Json.NET 属性。 But since you can generally modify your DTO you could mark it with a custom extension data attribute and then handle that attribute in either a custom generic JsonConverter or a custom contract resolver .但是由于您通常可以修改您的 DTO,您可以使用自定义扩展数据属性对其进行标记,然后在自定义通用JsonConverter自定义合同解析器中处理该属性。

Firstly , for an example of using a custom extension data attribute with a custom JsonConverter , see JsonTypedExtensionData from this answer to How to deserialize a child object with dynamic (numeric) key names?首先,有关将自定义扩展数据属性与自定义JsonConverter一起使用的示例,请参阅此答案中的JsonTypedExtensionData如何使用动态(数字)键名反序列化子 object?

Secondly , if you prefer not to use a converter, to handle a custom extension data attribute with a custom contract resolver, first define the following contract resolver:其次,如果您不想使用转换器,要使用自定义合约解析器处理自定义扩展数据属性,请首先定义以下合约解析器:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class MyJsonExtensionDataAttribute : Attribute
{
}

public class MyContractResolver : DefaultContractResolver
{
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);
        if (contract.ExtensionDataGetter == null && contract.ExtensionDataSetter == null)
        {
            var dictionaryProperty = contract.Properties
                .Where(p => typeof(IDictionary<string, object>).IsAssignableFrom(p.PropertyType) && p.Readable && p.Writable)
                .Where(p => p.AttributeProvider.GetAttributes(typeof(MyJsonExtensionDataAttribute), false).Any())
                .SingleOrDefault();
            if (dictionaryProperty != null)
            {
                dictionaryProperty.Ignored = true;
                contract.ExtensionDataGetter = o => 
                    ((IDictionary<string, object>)dictionaryProperty.ValueProvider.GetValue(o)).Select(p => new KeyValuePair<object, object>(p.Key, p.Value));
                contract.ExtensionDataSetter = (o, key, value) =>
                    {
                        var dictionary = (IDictionary<string, object>)dictionaryProperty.ValueProvider.GetValue(o);
                        if (dictionary == null)
                        {
                            dictionary = (IDictionary<string, object>)this.ResolveContract(dictionaryProperty.PropertyType).DefaultCreator();
                            dictionaryProperty.ValueProvider.SetValue(o, dictionary);
                        }
                        dictionary.Add(key, value);
                    };
                }
                contract.ExtensionDataValueType = typeof(object);
                // TODO set contract.ExtensionDataNameResolver
        }
        return contract;
    }
}

Then modify your DTO as follows:然后按如下方式修改您的 DTO:

public class MyDto
{
    [MyJsonExtensionData]
    public Dictionary<string, object> Identifier { get; set; }

    public double Value1 { get; set; }

    public string Value2 { get; set; }
}

And serialize as follows, caching a static instance of your resolver for performance:并按如下方式进行序列化,缓存解析器的 static 实例以提高性能:

static IContractResolver resolver = new MyContractResolver();

// And later

    var settings = new JsonSerializerSettings
    {
        ContractResolver = resolver,
    };

    var json = JsonConvert.SerializeObject(results, Formatting.Indented, settings);

Notes:笔记:

  • The MyJsonExtensionData property's type must be assignable to type IDictionary<string, object> and have a public, parameterless constructor. MyJsonExtensionData属性的类型必须可分配给类型IDictionary<string, object>并具有公共的无参数构造函数。

  • Naming strategies for extension data property names are not implemented.未实现扩展数据属性名称的命名策略。

  • Json.NET serializes extension data attributes at the end of each object whereas your question shows the custom attributes at the beginning. Json.NET 在每个 object 的末尾序列化扩展数据属性,而您的问题在开头显示自定义属性。 Since a JSON object is defined to be an unordered set of name/value pairs by the standard I think this should not matter.由于标准将 JSON object 定义为一组无序的名称/值对,因此我认为这无关紧要。 But if you require the custom properties at the beginning of your object, you may need to use a custom converter rather than a custom contract resolver.但是,如果您需要 object 开头的自定义属性,则可能需要使用自定义转换器而不是自定义合约解析器。

Demo fiddle here .演示小提琴在这里

Since your MyDto class is in a separate assembly for which you have limitations in the kinds of changes you can make, then yes, I think your best bet is to create a custom converter for the class.由于您的MyDto class 位于单独的组件中,因此您可以进行的更改种类受到限制,那么是的,我认为您最好的选择是为 class 创建一个自定义转换器。 Something like this should work:像这样的东西应该工作:

public class MyDtoConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(MyDto);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        MyDto dto = (MyDto)value;
        writer.WriteStartObject();
        writer.WritePropertyName(dto.Identifier.Key);
        writer.WriteValue(dto.Identifier.Value);
        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(typeof(MyDto));
        foreach (JsonProperty prop in contract.Properties.Where(p => p.PropertyName != nameof(MyDto.Identifier)))
        {
            writer.WritePropertyName(prop.PropertyName);
            writer.WriteValue(prop.ValueProvider.GetValue(dto));
        }
        writer.WriteEndObject();
    }

    public override bool CanRead => false;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

To use it you will need to add the converter to your Web API configuration:要使用它,您需要将转换器添加到 Web API 配置中:

config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new MyDtoConverter());

Here is a working demo of the converter in a console app: https://dotnetfiddle.net/DksgMZ这是控制台应用程序中转换器的工作演示: https://dotnetfiddle.net/DksgMZ

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM