Json数组到C#中的自定义对象映射 [英] Json Array to Custom Object Mapping in C#

查看:128
本文介绍了Json数组到C#中的自定义对象映射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个JSON数组作为输入.

I have this JSON Array as input.

[
    {
        "FirstName": "Test1",
        "LastName": "Test2",
        "Address": "London, GB",
        "Error": "Something's gone wrong"
    },
    {
        "FirstName": "Test3",
        "LastName": "Test4",
        "Address": "NewYork, US",
        "Error": "Something's gone wrong"
    },
    {
        "DisplayName": "ContactNumber",
        "Value": "01234 123 123"
    }
]

我想在C#中构建这样的JSON对象

I want to build a JSON Object like this in C#

[
    "pages":{
        "FirstName": "Test1",
        "LastName": "Test2",
        "Address": "London, GB",
        "Error": "Something's gone wrong"
    },
    {
        "FirstName": "Test3",
        "LastName": "Test4",
        "Address": "NewYork, US",
        "Error": "Something's gone wrong"
    },
  "labels": {
        "DisplayName": "ContactNumber",
        "Value": "01234 123 123"
  }
}
]

我已经使用上面的输出属性创建了一个Model,但是当我将该对象反序列化到那个Model中时,它们没有被映射.所有值都返回null.

I've created a Model with above output properties but they are not getting mapped when I deserialize the object into that Model. All values are returning null.

var deserializedData = JsonConvert.DeserializeObject<List<Config>>(serializedData);

我检查时收到的回复是

[
    {
        "pages": null,
        "labels": null
    },
    {
        "pages": null,
        "labels": null
    }
]

任何人都可以帮助我在 C#中为此格式构建自定义模型.

Can anyone help me build the Custom Model for this format I want in C#.

谢谢.

推荐答案

您拥有的是一个多态JSON数组,其中包含各种类型的对象,可以通过存在特定属性(例如"DisplayName" vs "FirstName")来区分. ).您想要做的是将其反序列化为c#模型,其中将每个可能的数组项添加到模型的collection-valued属性中,在该属性中,collection属性被选择为具有正确的项类型.

What you have is a polymorphic JSON array containing a variety of types of objects, distinguishable by the presence of specific properties ("DisplayName" vs "FirstName", e.g.). What you would like to do is to deserialize that into a c# model in which each possible array item gets added to a collection-valued property of your model, where the collection property is chosen to have the correct item type.

这可以通过使用自定义JsonConverter 来完成. .由于问题陈述是通用的,因此我将使转换器通用.硬编码转换器将需要更少的代码.

This can be accomplished by using a custom JsonConverter. Since the problem statement is generic I'm going to make the converter be generic. A hardcoded converter would require less code.

首先,如下定义所需的模型:

First, define your desired model as follows:

public class Page
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public string Error { get; set; }
}

public class Label
{
    public string DisplayName { get; set; }
    public string Value { get; set; }
}

public class RootObject
{
    public List<Page> pages { get; set; }
    public List<Label> labels { get; set; }
}

接下来定义以下转换器:

Next define the following converter:

public class PolymorphicArrayToObjectConverter<TObject> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(TObject).IsAssignableFrom(objectType);
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    static JsonObjectContract FindContract(JObject obj, IEnumerable<Type> derivedTypes, JsonSerializer serializer)
    {
        List<JsonObjectContract> bestContracts = new List<JsonObjectContract>();
        foreach (var type in derivedTypes)
        {
            if (type.IsAbstract)
                continue;
            var contract = serializer.ContractResolver.ResolveContract(type) as JsonObjectContract;
            if (contract == null)
                continue;
            if (obj.Properties().Select(p => p.Name).Any(n => contract.Properties.GetClosestMatchProperty(n) == null))
                continue;
            if (bestContracts.Count == 0 || bestContracts[0].Properties.Count > contract.Properties.Count)
            {
                bestContracts.Clear();
                bestContracts.Add(contract);
            }
            else if (contract.Properties.Count == bestContracts[0].Properties.Count)
            {
                bestContracts.Add(contract);
            }
        }
        return bestContracts.Single();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        else if (reader.TokenType != JsonToken.StartArray)
            throw new InvalidOperationException("JSON token is not an array at path: " + reader.Path);

        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
        existingValue = existingValue ?? contract.DefaultCreator();

        var lookup = contract
            .Properties
            .Select(p => new { Property = p, PropertyContract = serializer.ContractResolver.ResolveContract(p.PropertyType) as JsonArrayContract })
            .Where(i => i.PropertyContract != null)
            .ToDictionary(i => i.PropertyContract.CollectionItemType);
        var types = lookup.Select(i => i.Key).ToList();

        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                case JsonToken.EndArray:
                    return existingValue;
                default:
                    {
                        var itemObj = JObject.Load(reader);
                        var itemContract = FindContract(itemObj, types, serializer);
                        if (itemContract == null)
                            continue;
                        var item = serializer.Deserialize(itemObj.CreateReader(), itemContract.UnderlyingType);
                        var propertyData = lookup[itemContract.UnderlyingType];
                        var collection = propertyData.Property.ValueProvider.GetValue(existingValue);
                        if (collection == null)
                        {
                            collection = propertyData.PropertyContract.DefaultCreator();
                            propertyData.Property.ValueProvider.SetValue(existingValue, collection);
                        }
                        collection.GetType().GetMethod("Add").Invoke(collection, new [] { item });
                    }
                    break;
            }
        }
        // Should not come here.
        throw new JsonSerializationException("Unclosed array at path: " + reader.Path);
    }
}

然后,按如下所示反序列化并重新序列化RootObject集合:

Then, deserialize and re-serialize your RootObject collection as follows:

var settings = new JsonSerializerSettings
{
    Converters = { new PolymorphicArrayToObjectConverter<RootObject>() },
};
var root = new List<RootObject> { JsonConvert.DeserializeObject<RootObject>(jsonString, settings) };

var outputJson = JsonConvert.SerializeObject(root, Formatting.Indented);

因此,将生成以下JSON:

As a result, the following JSON will be generated:

[
  {
    "pages": [
      {
        "FirstName": "Test1",
        "LastName": "Test2",
        "Address": "London, GB",
        "Error": "Something's gone wrong"
      },
      {
        "FirstName": "Test3",
        "LastName": "Test4",
        "Address": "NewYork, US",
        "Error": "Something's gone wrong"
      }
    ],
    "labels": [
      {
        "DisplayName": "ContactNumber",
        "Value": "01234 123 123"
      }
    ]
  }
]

样本小提琴.

注意-自动推断数组项类型的代码改编自此答案.

Note - code to automatically infer the array item type was adapted from this answer.

这篇关于Json数组到C#中的自定义对象映射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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