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


回答:


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 2014

3
他にもオプションがありJsonConverterます。クラスのカスタムを作成できます。これは依存関係を削除しますが、その後、コンバーターで自分でオブジェクトのインスタンス化と入力を処理する必要があります。ContractResolverJson.Netにを変更することで他のコンストラクターを使用するように指示するカスタムを作成することもできますが、これは思ったより少し難しいかもしれませんJsonObjectContract
ブライアンロジャース

ええ、私は属性がうまく機能すると思います。逆シリアル化呼び出しは実際には総称的なものなので、どのような種類のオブジェクトでもかまいません。私はあなたの元の答えがうまくいくと思います。情報をありがとう!
kmacdonald 14

2
コンストラクタを選択するための別の規則を設定することが可能である場合、それは本当に役立ちます。たとえば、Unityコンテナーはこれをサポートしていると思います。次に、デフォルトに戻すのではなく、常にほとんどのパラメーターを持つコンストラクターを選択するように設定できます。Json.Netにそのような拡張ポイントが存在する可能性はありますか?
julealgon 2015

1
忘れないでくださいusing Newtonsoft.Json;
Bruno Bieri 2017年

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 2016年

1
構造体の制限(のチェックobjectType.IsValueType)を削除しましたが、これはうまく機能します。ありがとう!
Alex Angas

@AlexAngasはい、この戦略を一般的に適用することは、フィードバックに感謝する意味があります。
ゾルタンTamási

3

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

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

  • 単一のプライベートコンストラクターを選択すると、属性でマークする必要なく、1つのプライベートコンストラクターを定義できます。
  • 最も具体的なプライベートコンストラクターを選択して、属性を使用せずに複数のオーバーロードを作成できるようにします。
  • 特定の名前の属性でマークされたコンストラクターを選択します-デフォルトのリゾルバーのようですが、参照する必要があるため、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コンストラクターを見つけます。デフォルトのコンストラクターが含まれているクラスまたは同じアセンブリでのみ使用されている場合は、アクセスレベルをに下げるprotectedinternal、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; }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.