web-dev-qa-db-fra.com

JSON.net: comment désérialiser sans utiliser le constructeur par défaut?

J'ai une classe qui a un constructeur par défaut et également un constructeur surchargé qui prend en compte un ensemble de paramètres. Ces paramètres correspondent aux champs de l'objet et sont affectés lors de la construction. À ce stade, j'ai besoin du constructeur par défaut à d'autres fins, donc j'aimerais le conserver si je le peux. 

Mon problème: Si je supprime le constructeur par défaut et transmets la chaîne JSON, l'objet se désérialise correctement et transmet les paramètres du constructeur sans aucun problème. Je finis par récupérer l'objet rempli comme je m'y attendais. Cependant, dès que j'ajoute le constructeur par défaut à l'objet, lorsque j'appelle JsonConvert.DeserializeObject<Result>(jsontext), les propriétés ne sont plus renseignées.

À ce stade, j'ai essayé d'ajouter new JsonSerializerSettings(){CheckAdditionalContent = true} à l'appel de désérialisation. cela n'a rien fait. 

Une autre note. les paramètres du constructeur correspondent exactement aux noms des champs, sauf que les paramètres commencent par une lettre minuscule. Je ne pense pas que cela aurait de l'importance puisque, comme je l'ai mentionné, la désérialisation fonctionne bien sans constructeur par défaut. 

Voici un échantillon de mes constructeurs:

    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;
    }
106
kmacdonald

Json.Net préfère utiliser le constructeur par défaut (sans paramètre) sur un objet s'il en existe un. S'il existe plusieurs constructeurs et que vous souhaitez que Json.Net en utilise un autre que celui par défaut, vous pouvez ajouter l'attribut [JsonConstructor] au constructeur que vous souhaitez que Json.Net appelle.

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

Pour que cela fonctionne correctement, il est important que les noms de paramètre de constructeur correspondent aux noms de propriété correspondants de l'objet JSON (cas ignoré). Cependant, vous ne devez pas nécessairement avoir un paramètre de constructeur pour chaque propriété de l'objet. Pour les propriétés d'objet JSON qui ne sont pas couvertes par les paramètres du constructeur, Json.Net essaiera d'utiliser les accesseurs de propriété publique (ou les propriétés/champs marqués avec [JsonProperty]) pour renseigner l'objet après l'avoir construit.

Si vous ne souhaitez pas ajouter d'attributs à votre classe ou ne contrôlez pas le code source de la classe que vous essayez de désérialiser, vous pouvez également créer un JsonConverter personnalisé pour instancier et remplir votre objet. . Par exemple:

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

Ajoutez ensuite le convertisseur à vos paramètres de sérialiseur et utilisez-les lorsque vous désérialisez:

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

Un peu tard et pas tout à fait adapté ici, mais je vais ajouter ma solution ici, car ma question avait été fermée comme une copie de celle-ci, et parce que cette solution est complètement différente. 

J'avais besoin d'un moyen général d'enseigner à Json.NET de préférer le constructeur le plus spécifique pour un type de structure défini par l'utilisateur, afin de pouvoir omettre les attributs JsonConstructor qui ajouteraient une dépendance au projet où chaque telle structure est définie.

J'ai un peu ingénierie inverse et implémenté un résolveur de contrat personnalisé dans lequel j'ai substitué la méthode CreateObjectContract pour ajouter ma logique de création personnalisée.

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

Je l'utilise comme ç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");
25
Zoltán Tamási

Solution:

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

Modèle:

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

Sur la base de certaines des réponses ici, j’ai écrit une CustomConstructorResolver à utiliser dans un projet en cours, et j’ai pensé que cela pourrait aider quelqu'un d’autre.

Il prend en charge les mécanismes de résolution suivants, tous configurables:

  • Sélectionnez un seul constructeur privé afin de pouvoir définir un constructeur privé sans avoir à le marquer avec un attribut.
  • Sélectionnez le constructeur privé le plus spécifique afin de pouvoir avoir plusieurs surcharges, sans avoir à utiliser d'attributs.
  • Sélectionnez le constructeur marqué avec un attribut d'un nom spécifique, comme le résolveur par défaut, mais sans dépendance du package Json.Net, car vous devez référencer 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;
    }
}

Voici la version complète avec la documentation XML en tant que Gist: https://Gist.github.com/maverickelementalch/80f77f4b6bdce3b434b0f7a1d06baa95

Commentaires appréciés.

0
Björn Jarisch

Le comportement par défaut de Newtonsoft.Json va rechercher les constructeurs public. Si votre constructeur par défaut est uniquement utilisé dans une classe contenant le même assemblage, vous pouvez réduire le niveau d'accès à protected ou internal afin que Newtonsoft.Json sélectionne votre constructeur public souhaité.

Certes, cette solution est plutôt très limitée à des cas spécifiques.

    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;
    }
0
Jimmy Wu