wake-up-neo.com

JSON.net: Wie deserialisieren, ohne den Standardkonstruktor zu verwenden?

Ich habe eine Klasse, die einen Standardkonstruktor hat, und auch einen überladenen Konstruktor, der eine Reihe von Parametern aufnimmt. Diese Parameter stimmen mit Feldern des Objekts überein und werden bei der Konstruktion zugewiesen. An diesem Punkt brauche ich den Standardkonstruktor für andere Zwecke, also möchte ich ihn behalten, wenn ich kann. 

Mein Problem: Wenn ich den Standardkonstruktor entferne und die JSON-Zeichenfolge übergebe, deserialisiert das Objekt ordnungsgemäß und übergibt die Konstruktorparameter ohne Probleme. Am Ende bekomme ich das Objekt wieder so, wie ich es erwartet hatte. Sobald ich jedoch den Standardkonstruktor zum Objekt hinzufüge, werden beim Aufruf von JsonConvert.DeserializeObject<Result>(jsontext) die Eigenschaften nicht mehr ausgefüllt.

An dieser Stelle habe ich versucht, dem Deserialisierungsaufruf new JsonSerializerSettings(){CheckAdditionalContent = true} hinzuzufügen. das hat nichts getan. 

Noch eine Notiz. Die Konstruktorparameter stimmen mit den Namen der Felder genau überein, außer dass die Parameter mit einem Kleinbuchstaben beginnen. Ich würde nicht glauben, dass dies eine Rolle spielen würde, da, wie gesagt, die Deserialisierung ohne Standardkonstruktor funktioniert. 

Hier ist ein Beispiel meiner Konstruktoren:

    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 zieht es vor, den standardmäßigen (parameterlosen) Konstruktor für ein Objekt zu verwenden, sofern vorhanden. Wenn mehrere Konstruktoren vorhanden sind und Sie möchten, dass Json.Net einen nicht standardmäßigen verwendet, können Sie das [JsonConstructor]-Attribut dem Konstruktor hinzufügen, den Json.Net aufrufen soll.

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

Es ist wichtig, dass die Namen der Konstruktorparameter mit den entsprechenden Eigenschaftennamen des JSON-Objekts übereinstimmen (Groß- und Kleinschreibung ignorieren), damit dies ordnungsgemäß funktioniert. Sie müssen jedoch nicht für jede Eigenschaft des Objekts einen Konstruktorparameter haben. Für diese JSON-Objekteigenschaften, die nicht von den Konstruktorparametern abgedeckt werden, versucht Json.Net, die öffentlichen Eigenschaften-Accessoren (oder mit [JsonProperty] markierte Eigenschaften/Felder) zu verwenden, um das Objekt nach dem Erstellen zu füllen.

Wenn Sie Ihrer Klasse keine Attribute hinzufügen möchten oder den Quellcode für die Klasse, die Sie deserialisieren möchten, nicht anderweitig steuern, können Sie als Alternative einen benutzerdefinierten JsonConverter erstellen, um Ihr Objekt zu instanziieren und zu füllen. Zum Beispiel:

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

Fügen Sie dann den Konverter zu Ihren Serializer-Einstellungen hinzu und verwenden Sie die Einstellungen, wenn Sie die Deserialisierung durchführen:

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

Etwas spät und nicht gerade passend, aber ich werde hier meine Lösung hinzufügen, weil meine Frage als Duplikat dieser abgeschlossen wurde und weil diese Lösung völlig anders ist. 

Ich brauchte eine generelle Methode, um Json.NET anzuweisen, den spezifischsten Konstruktor für einen benutzerdefinierten Strukturtyp zu bevorzugen, sodass ich die JsonConstructor-Attribute weglassen kann, die dem Projekt, in dem jede solche Struktur definiert ist, eine Abhängigkeit hinzufügen.

Ich habe ein bisschen zurückentwickelt und einen benutzerdefinierten Vertragslöser implementiert, bei dem ich die CreateObjectContract-Methode überschrieben habe, um meine benutzerdefinierte Erstellungslogik hinzuzufügen.

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

Ich benutze es so.

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

Lösung:

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

Modell:

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

Basierend auf einigen Antworten hier habe ich eine CustomConstructorResolver geschrieben, die in einem aktuellen Projekt verwendet werden kann, und ich dachte, es könnte jemand anderem helfen.

Es unterstützt die folgenden Auflösungsmechanismen, die alle konfigurierbar sind:

  • Wählen Sie einen einzelnen privaten Konstruktor aus, damit Sie einen privaten Konstruktor definieren können, ohne ihn mit einem Attribut markieren zu müssen.
  • Wählen Sie den spezifischsten privaten Konstruktor aus, damit Sie mehrere Überladungen haben können, ohne Attribute verwenden zu müssen.
  • Wählen Sie den Konstruktor aus, der mit einem Attribut eines bestimmten Namens markiert ist - wie der Standardauflöser, jedoch ohne Abhängigkeit vom Json.Net-Paket, da Sie auf Newtonsoft.Json.JsonConstructorAttribute verweisen müssen.
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;
    }
}

Hier ist die vollständige Version mit XML-Dokumentation als Gist: https://Gist.github.com/maverickelementalch/80f77f4b6bdce3b434b0f7a1d06baa95

Feedback wird geschätzt.

0
Björn Jarisch

Das Standardverhalten von Newtonsoft.Json sucht nach den Konstruktoren public. Wenn Ihr Standardkonstruktor nur in enthaltenden Klassen oder derselben Assembly verwendet wird, können Sie die Zugriffsebene auf protected oder internal reduzieren, damit Newtonsoft.Json den gewünschten public -Konstruktor auswählt.

Zugegebenermaßen ist diese Lösung eher auf bestimmte Fälle beschränkt.

    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