web-dev-qa-db-ja.com

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

Json.Netは、オブジェクトにデフォルトの(パラメーターのない)コンストラクターがある場合はそれを使用することを好みます。複数のコンストラクターがあり、Json.Netでデフォルト以外のコンストラクターを使用する場合は、Json.Netに呼び出すコンストラクターに[JsonConstructor]属性を追加できます。

[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);
166
Brian Rogers

ここでは少し遅れており、正確には適していませんが、 私の質問 はこのものの複製として閉じられていたため、このソリューションは完全に異なるため、ここに私のソリューションを追加します。

ユーザー定義のstruct型に最も具体的なコンストラクターを優先するようにJson.NETに指示する一般的な方法が必要だったため、そのような各structが定義されているプロジェクトに依存関係を追加する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");
29
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;
}

モデル:

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

ここでの回答のいくつかに基づいて、現在のプロジェクトで使用するCustomConstructorResolverを作成しました。

以下の解決メカニズムをサポートし、すべて構成可能です。

  • 単一のプライベートコンストラクターを選択して、属性でマークする必要なく1つのプライベートコンストラクターを定義できるようにします。
  • 最も具体的なプライベートコンストラクターを選択すると、属性を使用せずに複数のオーバーロードを保持できます。
  • 特定の名前の属性でマークされたコンストラクターを選択します-デフォルトのリゾルバーと同様ですが、Newtonsoft.Json.JsonConstructorAttributeを参照する必要があるため、Json.Netパッケージに依存しません。
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ドキュメントをGistとして含む完全なバージョンを示します。 https://Gist.github.com/maverickelementalch/80f77f4b6bdce3b434b0f7a1d06baa95

フィードバックに感謝します。

0
Björn Jarisch

Newtonsoft.Jsonのデフォルトの動作では、publicコンストラクターが検索されます。デフォルトのコンストラクターが含まれるクラスまたは同じアセンブリでのみ使用される場合、Newtonsoft.Jsonが目的のprotectedコンストラクターを選択するように、アクセスレベルをinternalまたは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;
    }
0
Jimmy Wu