使用Json.Net反序列化嵌套属性,而无需使用数据注释

时间:2018-12-03 22:57:07

标签: c# json properties json.net nested-properties

我有一个可以从多个API获取数据的应用程序。 为了减少类的数量,我需要映射到每个属性。 我已经实现了一个简单的json.net >>> argv1_a=[] >>> argv1_b=[] >>> argv2_a=[] >>> argv2_b=[] >>> dic = {'1': ['1', 'x', 'build'], '2': ['x', '8', 'demolish'], '3': ['3', 'x', 'build'], '4': ['6', '10', 'demolish']} >>> d1 = dict(build=argv1_a, demolish=argv1_b) >>> d2 = dict(build=argv2_a, demolish=argv2_b) >>> for x, y, key in dic.values(): ... d1[key].append(int(x) if x.isdigit() else x) ... d2[key].append(int(y) if y.isdigit() else y) ... >>> print (argv1_a, argv1_b, argv2_a, argv2_b) [1, 3] ['x', 6] ['x', 'x'] [8, 10] 。 但是,当我尝试将一个属性映射到一个子属性时,遇到了一些麻烦。

JSON格式1:

ContractResolver

JSON格式2:

{
    "event_id": 123,
    "event_name": "event1",
    "start_date": "2018-11-30",
    "end_date": "2018-12-04",
    "participants": {
        "guests": [
            {
                "guest_id": 143,
                "first_name": "John",
                "last_name": "Smith",               
            },
            {
                "guest_id": 189,
                "first_name": "Bob",
                "last_name": "Duke",    
            }
        ]
    }
}

这是我的模型课:

{
    "name": "event2",
    "from": "2017-05-05",
    "to": "2017-05-09",
    "city":"Some other city",
    "country":"US",
    "guests": [
        {
            "email":"jane@smith.com",
            "firstName":"Jane",
            "lastName":"Smith",
            "telephone":"1-369-81891"
        }
    ],
}

还有我的解析器:

public class Event
{
    public int EventId { get; set; }
    public string EventName { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public List<Guest> Guests { get; set; }
}

public class Guest
{
    public string GuestId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }       
}

我知道,路径不能代替属性名称。怎么会这样呢?

2 个答案:

答案 0 :(得分:0)

我认为您过度设计了它,当您需要支持越来越多的格式和制作人时,它将变得一团糟。试想一下,如果您有15个具有不同格式的事件生产者,那么您的解析器将是什么样?

您需要为您的域创建一组适合您的域和需求的类。

public class Event
{
    public int EventId { get; set; }
    public string EventName { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public List<Guest> Guests { get; set; }
}

public class Guest
{
    public string GuestId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }       
}

public interface IEventProvider
{
    Event[] GetEvents();
}

然后为每个外部生产者创建一组类并将其映射到您的域类,例如使用AutoMapper配置文件或手动将其映射到您的域类。

namespace YourCompany.EventProvider.Api1
{
    // just an example with json2sharp, use data annotations if you want
    public class Guest
    {
        public int guest_id { get; set; }
        public string first_name { get; set; }
        public string last_name { get; set; }
    }

    public class Participants
    {
        public List<Guest> guests { get; set; }
    }

    public class RootObject
    {
        public int event_id { get; set; }
        public string event_name { get; set; }
        public string start_date { get; set; }
        public string end_date { get; set; }
        public Participants participants { get; set; }
    }

    public class Api1EventProvider : IEventProvider
    {
        public Event[] GetEvents()
        {
           RootObject[] api1Response = GetFromApi();
           return _mapper.Map<RootObject[], Event[]>(api1Response);
        }
    }       
}

是的,我将再上更多课。但是此代码将更好,更易读和可维护。与创建解析器相比,您将花费更少的时间来创建它;而且将来的开发人员不会每次生产者更改其API时都会哭泣。
代码质量不是要创建更少的类。

答案 1 :(得分:0)

我认为解析器的想法行不通,因为您不仅在重新映射属性名称,而且还试图反序列化为不总是与JSON形状匹配的类结构。这项工作更适合一组JsonConverter

这是基本方法:

  1. 为每个JSON有所不同的模型类创建一个JsonConverter
  2. ReadJson方法内部,从阅读器加载JObject
  3. 通过查找始终存在的已知属性名称来检测该格式。例如,如果您可以依靠event_id始终以第一种格式出现,那是检测它的好方法,因为您知道第二种格式不具有该属性。您可以根据需要检查是否存在多个属性。关键是要使用仅以一种格式出现而没有其他格式出现的某种组合。 (或者,如果您提前知道所需的格式,则可以简单地对转换器进行参数化,即在构造函数中传递格式标志。)
  4. 一旦知道了格式,就从JObject填充模型。

对于您问题中显示的Event模型,转换器可能看起来像这样:

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        Event evt = new Event();
        JObject obj = JObject.Load(reader);
        if (obj["event_id"] != null)
        {
            // JSON format #1
            evt.EventId = (int)obj["event_id"];
            evt.EventName = (string)obj["event_name"];
            evt.StartDate = (DateTime)obj["start_date"];
            evt.EndDate = (DateTime)obj["end_date"];
            evt.Guests = obj.SelectToken("participants.guests").ToObject<List<Guest>>(serializer);
        }
        else if (obj["name"] != null)
        {
            // JSON format #2
            evt.EventName = (string)obj["name"];
            evt.StartDate = (DateTime)obj["from"];
            evt.EndDate = (DateTime)obj["to"];
            evt.Guests = obj["guests"].ToObject<List<Guest>>(serializer);
        }
        else
        {
            throw new JsonException("Unknown format for Event");
        }
        return evt;
    }

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

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

类似地,对于Guest模型,我们可能会有这个JsonConverter

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        Guest guest = new Guest();
        JObject obj = JObject.Load(reader);
        if (obj["guest_id"] != null)
        {
            // JSON format #1
            guest.GuestId = (string)obj["guest_id"];
            guest.FirstName = (string)obj["first_name"];
            guest.LastName = (string)obj["last_name"];
        }
        else if (obj["email"] != null)
        {
            // JSON format #2
            guest.FirstName = (string)obj["firstName"];
            guest.LastName = (string)obj["lastName"];
            guest.Email = (string)obj["email"];
        }
        else
        {
            throw new JsonException("Unknown format for Guest");
        }
        return guest;
    }

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

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

要使用转换器,请将它们添加到Converters对象的JsonSerializerSettings集合中,然后将设置传递给DeserializeObject(),如下所示:

var settings = new JsonSerializerSettings
{
    Converters = new List<JsonConverter> { new EventConverter(), new GuestConverter() }
};

var evt = JsonConvert.DeserializeObject<Event>(json, settings);

演示小提琴:https://dotnetfiddle.net/KI82KB