JSON.net: как десериализовать без использования конструктора по умолчанию?

У меня есть класс, который имеет конструктор по умолчанию, а также перегруженный конструктор, который принимает набор параметров. Эти параметры соответствуют полям объекта и назначаются при построении. На данный момент мне нужен конструктор по умолчанию для других целей, поэтому я хотел бы сохранить его, если смогу.

Моя проблема: если я удаляю конструктор по умолчанию и передаю строку JSON, объект десериализуется правильно и передает параметры конструктора без каких-либо проблем. В итоге я возвращаюсь к объекту, заполненному так, как я ожидал. Однако, как только я добавлю конструктор по умолчанию в объект, когда я вызываю JsonConvert.DeserializeObject<Result>(jsontext), свойства больше не заполняются.

В этот момент я попытался добавить new JsonSerializerSettings(){CheckAdditionalContent = true} к вызову десериализации. что ничего не делало.

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

Вот пример моих конструкторов:

    public Result() { }

    public Result(int? code, string format, Dictionary<string, string> details = null)
    {
        Code = code ?? ERROR_CODE;
        Format = format;

        if (details == null)
            Details = new Dictionary<string, string>();
        else
            Details = details;
    }

Ответ 1

Json.Net предпочитает использовать конструктор по умолчанию (без параметров) для объекта, если он есть. Если есть несколько конструкторов, и вы хотите, чтобы Json.Net использовал нестандартный, вы можете добавить атрибут [JsonConstructor] к конструктору, который вы хотите вызвать Json.Net.

[JsonConstructor]
public Result(int? code, string format, Dictionary<string, string> details = null)
{
    ...
}

Важно, чтобы имена параметров конструктора соответствовали соответствующим именам свойств объекта JSON (игнорируя случай), чтобы это работало правильно. Однако необязательно иметь параметр конструктора для каждого свойства объекта. Для тех свойств объекта JSON, которые не охвачены параметрами конструктора, Json.Net попытается использовать общедоступные аксессоры (или свойства/поля, помеченные [JsonProperty]), чтобы заполнить объект после его создания.

Если вы не хотите добавлять атрибуты в свой класс или иначе не управляете исходным кодом для класса, который вы пытаетесь десериализовать, то другой альтернативой является создание пользовательского JsonConverter, чтобы создать экземпляр и заполнить ваш объект. Например:

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load the JSON for the Result into a JObject
        JObject jo = JObject.Load(reader);

        // Read the properties which will be used as constructor parameters
        int? code = (int?)jo["Code"];
        string format = (string)jo["Format"];

        // Construct the Result object using the non-default constructor
        Result result = new Result(code, format);

        // (If anything else needs to be populated on the result object, do that here)

        // Return the result
        return result;
    }

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

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

Затем добавьте конвертер в настройки вашего сериализатора и используйте настройки при десериализации:

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Result result = JsonConvert.DeserializeObject<Result>(jsontext, settings);

Ответ 2

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

Мне нужен общий способ проинструктировать Json.NET, чтобы предпочесть наиболее специфический конструктор для определяемого пользователем типа структуры, поэтому я могу опустить атрибуты JsonConstructor, которые добавили бы зависимость к проекту, где каждая такая структура определена.

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

public class CustomContractResolver : DefaultContractResolver {

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var c = base.CreateObjectContract(objectType);
        if (!IsCustomStruct(objectType)) return c;

        IList<ConstructorInfo> list = objectType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).OrderBy(e => e.GetParameters().Length).ToList();
        var mostSpecific = list.LastOrDefault();
        if (mostSpecific != null)
        {
            c.OverrideCreator = CreateParameterizedConstructor(mostSpecific);
            c.CreatorParameters.AddRange(CreateConstructorParameters(mostSpecific, c.Properties));
        }

        return c;
    }

    protected virtual bool IsCustomStruct(Type objectType)
    {
        return objectType.IsValueType && !objectType.IsPrimitive && !objectType.IsEnum && !objectType.Namespace.IsNullOrEmpty() && !objectType.Namespace.StartsWith("System.");
    }

    private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
    {
        method.ThrowIfNull("method");
        var c = method as ConstructorInfo;
        if (c != null)
            return a => c.Invoke(a);
        return a => method.Invoke(null, a);
    }
}

Я использую его вот так.

public struct Test {
  public readonly int A;
  public readonly string B;

  public Test(int a, string b) {
    A = a;
    B = b;
  }
}

var json = JsonConvert.SerializeObject(new Test(1, "Test"), new JsonSerializerSettings {
  ContractResolver = new CustomContractResolver()
});
var t = JsonConvert.DeserializeObject<Test>(json);
t.A.ShouldEqual(1);
t.B.ShouldEqual("Test");

Ответ 3

Основываясь на некоторых ответах здесь, я написал CustomConstructorResolver для использования в текущем проекте, и я подумал, что это может помочь кому-то еще.

Он поддерживает следующие механизмы разрешения, все настраиваемые:

  • Выберите один частный конструктор, чтобы вы могли определить один частный конструктор без необходимости отмечать его атрибутом.
  • Выберите наиболее конкретный частный конструктор, чтобы вы могли иметь несколько перегрузок без использования атрибутов.
  • Выберите конструктор, помеченный атрибутом с конкретным именем - например, распознаватель по умолчанию, но без зависимости от пакета Json.Net, поскольку вам нужно ссылаться на Newtonsoft.Json.JsonConstructorAttribute.
public class CustomConstructorResolver : DefaultContractResolver
{
    public string ConstructorAttributeName { get; set; } = "JsonConstructorAttribute";
    public bool IgnoreAttributeConstructor { get; set; } = false;
    public bool IgnoreSinglePrivateConstructor { get; set; } = false;
    public bool IgnoreMostSpecificConstructor { get; set; } = false;

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);

        // Use default contract for non-object types.
        if (objectType.IsPrimitive || objectType.IsEnum) return contract;

        // Look for constructor with attribute first, then single private, then most specific.
        var overrideConstructor = 
               (this.IgnoreAttributeConstructor ? null : GetAttributeConstructor(objectType)) 
            ?? (this.IgnoreSinglePrivateConstructor ? null : GetSinglePrivateConstructor(objectType)) 
            ?? (this.IgnoreMostSpecificConstructor ? null : GetMostSpecificConstructor(objectType));

        // Set override constructor if found, otherwise use default contract.
        if (overrideConstructor != null)
        {
            SetOverrideCreator(contract, overrideConstructor);
        }

        return contract;
    }

    private void SetOverrideCreator(JsonObjectContract contract, ConstructorInfo attributeConstructor)
    {
        contract.OverrideCreator = CreateParameterizedConstructor(attributeConstructor);
        contract.CreatorParameters.Clear();
        foreach (var constructorParameter in base.CreateConstructorParameters(attributeConstructor, contract.Properties))
        {
            contract.CreatorParameters.Add(constructorParameter);
        }
    }

    private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
    {
        var c = method as ConstructorInfo;
        if (c != null)
            return a => c.Invoke(a);
        return a => method.Invoke(null, a);
    }

    protected virtual ConstructorInfo GetAttributeConstructor(Type objectType)
    {
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
            .Where(c => c.GetCustomAttributes().Any(a => a.GetType().Name == this.ConstructorAttributeName)).ToList();

        if (constructors.Count == 1) return constructors[0];
        if (constructors.Count > 1)
            throw new JsonException($"Multiple constructors with a {this.ConstructorAttributeName}.");

        return null;
    }

    protected virtual ConstructorInfo GetSinglePrivateConstructor(Type objectType)
    {
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);

        return constructors.Length == 1 ? constructors[0] : null;
    }

    protected virtual ConstructorInfo GetMostSpecificConstructor(Type objectType)
    {
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
            .OrderBy(e => e.GetParameters().Length);

        var mostSpecific = constructors.LastOrDefault();
        return mostSpecific;
    }
}

Вот полная версия с XML-документацией в виде сущности: https://gist.github.com/maverickelementalch/80f77f4b6bdce3b434b0f7a1d06baa95

Обратная связь приветствуется.

Ответ 4

Поведение по умолчанию в Newtonsoft.Json будет искать public конструкторы. Если ваш конструктор по умолчанию используется только в содержащем классе или той же сборке, вы можете уменьшить уровень доступа до protected или internal чтобы Newtonsoft.Json выбрал желаемый public конструктор.

Следует признать, что это решение весьма ограничено конкретными случаями.

    internal Result() { }

    public Result(int? code, string format, Dictionary<string, string> details = null)
    {
        Code = code ?? ERROR_CODE;
        Format = format;

        if (details == null)
            Details = new Dictionary<string, string>();
        else
            Details = details;
    }

Ответ 5

Решение:

public Response Get(string jsonData) {
    var json = JsonConvert.DeserializeObject<modelname>(jsonData);
    var data = StoredProcedure.procedureName(json.Parameter, json.Parameter, json.Parameter, json.Parameter);
    return data;
}

Модель:

public class modelname {
    public long parameter{ get; set; }
    public int parameter{ get; set; }
    public int parameter{ get; set; }
    public string parameter{ get; set; }
}