Сопоставление плоского JSON/словаря с моделью (содержащей подклассы)

Я хочу превратить плоскую строку json в модель, целевой класс имеет подклассы, а плоский json имеет все объекты подкласса с префиксом; например "{имя класса}.{свойство}".

{
    "FirstName": "Joey",
    "LastName": "Billy",
    "EmploymentDetails.JobTitle": "JobTitle",
    "EmploymentDetails.StartDate": "2015-01-01T00:00:00",
    "ContactDetails.HouseNumberName": "10",
    "ContactDetails.Road": "Road"
}

Это мой целевой класс:

public class Person {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public virtual EmploymentDetails EmploymentDetails { get;set;}
    public virtual ContactDetails ContactDetails { get;set;}
}
public class EmploymentDetails {
    public string JobTitle { get; set; }
    public DateTime StartDate { get; set; }
}
public class ContactDetails {
    public string HouseNumberName { get; set; }
    public string Road { get; set; }
}

Я пробовал следующее:

public static void main() {
    var json = @"{""FirstName"": ""Joey"",""LastName"": ""Billy"",""EmploymentDetails.JobTitle"": ""JobTitle"",""EmploymentDetails.StartDate"": ""2015-01-01T00:00:00"",""ContactDetails.HouseNumberName"": ""10"",""ContactDetails.Road"": ""Road"",}";

    //try using AutoMapper
    Mapper.CreateMap<string,Person>();
    var personModel = Mapper.Map<Person>(json);
    //just returns null values

    //try using Newtonsoft
    personModel = Newtonsoft.Json.JsonConvert.DeserializeObject<Person>(json);
    //fills values but obviously returns no nested data
}

Я знаю, что Automapper имеет RecognizePrefix и RecognizeDestinationPrefix, но AutoMapper, кажется, заботится только о том, находится ли он в исходном объекте, а не в подклассе.

Потенциально я мог бы взять свою строку JSON и сделать ее словарем, но даже тогда я не знаю, как сопоставить ее с моделью с подклассами.

Была надежда, что у меня может быть бесконечное количество подклассов, а строка JSON может просто сопоставить плоскую модель JSON с моделью.


person Antoine Delaître    schedule 08.10.2015    source источник


Ответы (1)


Вы можете создать JsonConverter, который делает это общим способом, используя ContractResolver для группировки и заполнения свойств в десериализуемом классе или содержащихся в нем классах. по мере необходимости.

Вы не запрашивали сериализацию, только десериализацию, вот что это делает:

public class JsonFlatteningConverter : JsonConverter
{
    readonly IContractResolver resolver;

    public JsonFlatteningConverter(IContractResolver resolver)
    {
        if (resolver == null)
            throw new ArgumentNullException();
        this.resolver = resolver;
    }

    public override bool CanConvert(Type objectType)
    {
        return resolver.ResolveContract(objectType) is JsonObjectContract;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        JObject jObject = JObject.Load(reader);
        var contract = (JsonObjectContract)resolver.ResolveContract(objectType); // Throw an InvalidCastException if this object does not map to a JObject.

        existingValue = existingValue ?? contract.DefaultCreator();

        if (jObject.Count == 0)
            return existingValue;

        var groups = jObject.Properties().GroupBy(p => p.Name.Contains('.') ? p.Name.Split('.').FirstOrDefault() : null).ToArray();
        foreach (var group in groups)
        {
            if (string.IsNullOrEmpty(group.Key))
            {
                var subObj = new JObject(group);
                using (var subReader = subObj.CreateReader())
                    serializer.Populate(subReader, existingValue);
            }
            else
            {
                var jsonProperty = contract.Properties[group.Key];
                if (jsonProperty == null || !jsonProperty.Writable)
                    continue;
                if (jsonProperty != null)
                {
                    var subObj = new JObject(group.Select(p => new JProperty(p.Name.Substring(group.Key.Length + 1), p.Value)));
                    using (var subReader = subObj.CreateReader())
                    {
                        var propertyValue = serializer.Deserialize(subReader, jsonProperty.PropertyType);
                        jsonProperty.ValueProvider.SetValue(existingValue, propertyValue);
                    }
                }
            }
        }
        return existingValue;
    }

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

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

И затем используйте его таким образом:

        var resolver = new DefaultContractResolver();
        var settings = new JsonSerializerSettings { ContractResolver = resolver, Converters = new JsonConverter[] { new JsonFlatteningConverter(resolver) } };

        var person = JsonConvert.DeserializeObject<Person>(json, settings);

Прототип скрипки.

person dbc    schedule 09.10.2015
comment
Ты абсолютный зверь!! Да, сериализация — это пустяк, я просто знал, что для этого есть лучший способ, я также шел по пути JObject и отражения, но путь, которым я шел, был неприятным. - person Antoine Delaître; 09.10.2015
comment
И вы также учли дальнейшие подклассы! Таким образом, ContactDetails.County.CountyName или EmploymentDetails.Employer.CompanyName также будут работать, предполагая соответствующие классы или там. Ваше здоровье. - person Antoine Delaître; 09.10.2015
comment
Было бы здорово, если бы это работало и с dynamic. В настоящее время выдает ошибку при попытке получить jsonProperty, так как ее нет. - person GFoley83; 25.10.2017