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


136

У мене клас, який має конструктор за замовчуванням, а також перевантажений конструктор, який приймає набір параметрів. Ці параметри відповідають полям на об'єкті і призначаються при побудові. На даний момент мені потрібен конструктор за замовчуванням для інших цілей, тому я хотів би зберегти його, якщо зможу.

Моя проблема: Якщо я видаляю конструктор за замовчуванням і передаю в рядок 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;
}

Може бути , це може допомогти stackoverflow.com/questions/8254503 / ...
csharpwinphonexaml

Відповіді:


208

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

4
Це спрацювало. Це неначе гарно, що мені зараз доводиться брати залежність від JSON.net у проекті моїх моделей, але що таке. Я позначу це як відповідь.
kmacdonald

3
Є й інші варіанти - ви можете створити спеціальний JsonConverterдля свого класу. Це призведе до усунення залежності, але тоді вам доведеться самостійно обробляти екземпляри та заповнювати об'єкт у перетворювачі. Можливо, також можна написати звичай, ContractResolverякий спрямовував Json.Net використовувати інший конструктор, змінюючи його JsonObjectContract, але це може виявитися трохи складніше, ніж це звучить.
Брайан Роджерс

Так, я думаю, що атрибут буде добре працювати. Виклик десяриалізації насправді є загальним, так що він може бути будь-яким типом об'єкта. Я думаю, що ваша оригінальна відповідь буде добре працювати. дякую за інформацію!
kmacdonald

2
Це дійсно допоможе, якби вдалося встановити інший конвент для вибору конструктора. Наприклад, я думаю, що контейнер Unity підтримує це. Тоді ви можете зробити так, щоб він завжди вибирав конструктор з більшістю параметрів, а не повертався до стандартного. Є можливість такої точки розширення існувати в Json.Net?
julealgon

1
Не забувайтеusing Newtonsoft.Json;
Бруно Бієрі

36

Трохи пізно і не зовсім підходить тут, але я додам тут своє рішення, тому що моє запитання було закрито як дублікат цього і тому, що це рішення зовсім інше.

Мені потрібен був загальний спосіб доручити 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");

2
Зараз я використовую прийняту відповідь вище, але хочу подякувати за те, що ви також показали своє рішення!
DotBert

1
Я зняв обмеження на структури (чек на objectType.IsValueType), і це чудово працює, дякую!
Алекс Ангас

@AlexAngas Так, загалом, застосовуючи цю стратегію, має сенс дякувати за відгук.
Золтан Тамасі

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

Відгуки оцінені.


Чудове рішення! Дякую, що поділились.
томаї

1

Поведінка 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;
}

-1

Рішення:

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; }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.