Десериализовать вложенные свойства с помощью Json.Net без использования аннотаций данных - PullRequest
0 голосов
/ 04 декабря 2018

У меня есть приложение, которое получает данные от нескольких API.Чтобы свести к минимуму количество классов, мне нужно сопоставить каждое свойство.Я реализовал простой json.net ContractResolver.Однако, когда я пытаюсь сопоставить свойство дочернему свойству, у меня возникают некоторые проблемы.

Формат JSON 1:

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

Формат JSON 2:

{
    "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; }       
}

И мой преобразователь:

public class EventResolver : DefaultContractResolver
{
    private Dictionary<string,string> PropertyMappings { get; set; }

    public EventResolver()
    {
        this.PropertyMappings = new Dictionary<string, string>
        {
            {"EventId", "event_id"},
            {"StartDate", "start_date" },
            {"EndDate", "end_date" },
            {"EventName", "event_name" },
            {"Guests", "participants.guests"}
        };
    }

    protected override JsonContract CreateContract(Type objectType)
    {
        return base.CreateContract(objectType);
    }

    protected override string ResolvePropertyName(string propertyName)
    {
        var resolved = this.PropertyMappings.TryGetValue(propertyName, out var resolvedName);
        return (resolved) ? resolvedName : base.ResolvePropertyName(propertyName);
    }
}

Я понимаю, что путь не будет работать вместо имени свойства.Как можно это сделать?

Ответы [ 2 ]

0 голосов
/ 05 декабря 2018

Я не думаю, что идея распознавателя сработает, потому что вы переопределяете не только имена свойств - вы также пытаетесь десериализовать в структуру класса, которая не всегда соответствует форме JSON.Это задание лучше подходит для набора JsonConverter с.

Вот базовый подход:

  1. Создайте один JsonConverter для каждого класса модели, для которого изменяется JSON.
  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

0 голосов
/ 04 декабря 2018

Я думаю, что вы переоцениваете это, и это станет беспорядком, когда вам потребуется поддерживать все больше и больше форматов и производителей.Представьте себе, если у вас есть 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.
Качество кода не сводится к созданию меньшего количества классов.

...