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


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

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

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

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

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

    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;
    }
2 98

2 ответа:

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);

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

мне нужен был общий способ инструктировать 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");