Json.NET - поведение десериализации по умолчанию для одного свойства в CustomCreationConverter

В следующем сценарии, как мне получить CrazyItemConverter для продолжения, как обычно, когда он встречает свойство JSON, которое существует в дескрипции типа, который я десериализую?

У меня есть JSON, который выглядит так:

{
    "Item":{
        "Name":"Apple",
        "Id":null,
        "Size":5,
        "Quality":2
    }
}

JSON десериализуется в класс, который выглядит так:

[JsonConverter(typeof(CrazyItemConverter))]
public class Item
{
    [JsonConverter(typeof(CrazyStringConverter))]
    public string Name { get; set; }

    public Guid? Id { get; set; }

    [JsonIgnore]
    public Dictionary<string, object> CustomFields
    {
        get
        {
            if (_customFields == null)
                _customFields = new Dictionary<string, object>();
            return _customFields;
        }
    }

    ...
}

CrazyItemConverter заполняет значения известных свойств и помещает неизвестные свойства в CustomFields. ReadJson в нем выглядит так:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    var outputObject = Create(objectType);
    var objProps = objectType.GetProperties().Select(p => p.Name).ToArray();

    while (reader.Read())
    {
        if (reader.TokenType == JsonToken.PropertyName)
        {
            string propertyName = reader.Value.ToString();
            if (reader.Read())
            {
                if (objProps.Contains(propertyName))
                {
                    // No idea :(
                    // serializer.Populate(reader, outputObject);
                }
                else
                {
                    outputObject.AddProperty(propertyName, reader.Value);
                }
            }
        }
    }
    return outputObject;
}

Во время десериализации, когда CrazyItemConverter встречает известное свойство, я хочу, чтобы оно действовало так, как обычно. Значение, соблюдая [JsonConverter(typeof(CrazyStringConverter))] для Name.

Я использовал код ниже, чтобы установить известные свойства, но он выдает исключения из nullables и не уважает мои другие JsonConverters.

PropertyInfo pi = outputObject.GetType().GetProperty(readerValue, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var convertedValue = Convert.ChangeType(reader.Value, pi.PropertyType);
pi.SetValue(outputObject, convertedValue, null);

Любые идеи?

ОБНОВЛЕНИЕ: Я узнал, что serializer.Populate(reader, outputObject); - это десериализация всего этого, но он не работает, если вы хотите использовать функциональные возможности по умолчанию для свойства по свойствам.

Ответ 1

Если я правильно понимаю, ваш CrazyItemConverter существует так, что вы можете десериализовать известные свойства в JSON для строго типизированных свойств, сохраняя при этом все лишние поля, которые могут быть в JSON в словаре.

Оказывается, у Json.Net уже есть эта встроенная функция (начиная с версии 5.0 версии 5), поэтому вам не нужен сумасшедший конвертер. Вместо этого вам просто нужно пометить словарь с помощью атрибута [JsonExtensionData]. (См. блог автора для получения дополнительной информации.)

Итак, ваш класс Item будет выглядеть следующим образом:

public class Item
{
    [JsonConverter(typeof(CrazyStringConverter))]
    public string Name { get; set; }

    public Guid? Id { get; set; }

    [JsonExtensionData]
    public Dictionary<string, object> CustomFields
    {
        get
        {
            if (_customFields == null)
                _customFields = new Dictionary<string, object>();
            return _customFields;
        }
        private set
        {
            _customFields = value;
        }
    }
    private Dictionary<string, object> _customFields;
}

Тогда вы можете просто десериализировать его как обычно. Демо-ролик:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        {
            ""Item"":
            {
                ""Name"":""Apple"",
                ""Id"":""4b7e9f9f-7a30-4f79-8e47-8b50ea26ddac"",
                ""Size"":5,
                ""Quality"":2
            }
        }";

        Item item = JsonConvert.DeserializeObject<Wrapper>(json).Item;
        Console.WriteLine("Name: " + item.Name);
        Console.WriteLine("Id: " + item.Id);
        foreach (KeyValuePair<string, object> kvp in item.CustomFields)
        {
            Console.WriteLine(kvp.Key + ": " + kvp.Value);
        }
    }
}

public class Wrapper
{
    public Item Item { get; set; }
}

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        // Reverse the string just for fun
        return new string(token.ToString().Reverse().ToArray());
    }

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

Выход:

Name: elppA
Id: 4b7e9f9f-7a30-4f79-8e47-8b50ea26ddac
Size: 5
Quality: 2