JSON.net: bagaimana cara deserialisasi tanpa menggunakan konstruktor default?

136

Saya memiliki kelas yang memiliki konstruktor default dan juga konstruktor kelebihan beban yang menggunakan satu set parameter. Parameter ini cocok dengan bidang pada objek dan ditugaskan pada konstruksi. Pada titik ini saya memerlukan konstruktor default untuk keperluan lain jadi saya ingin menyimpannya jika saya bisa.

Masalah saya: Jika saya menghapus konstruktor default dan meneruskan string JSON, objek deserializes dengan benar dan melewati parameter konstruktor tanpa masalah. Saya akhirnya mendapatkan kembali objek yang dihuni seperti yang saya harapkan. Namun, segera setelah saya menambahkan konstruktor default ke objek, ketika saya memanggil JsonConvert.DeserializeObject<Result>(jsontext)properti tidak lagi diisi.

Pada titik ini saya telah mencoba menambahkan new JsonSerializerSettings(){CheckAdditionalContent = true}panggilan deserialization. itu tidak melakukan apa-apa.

Catatan lain. parameter konstruktor benar-benar cocok dengan nama-nama bidang kecuali bahwa parameter dimulai dengan huruf kecil. Saya tidak akan berpikir ini akan menjadi masalah karena, seperti yang saya sebutkan, deserialisasi berfungsi dengan baik tanpa konstruktor default.

Berikut ini contoh konstruktor saya:

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

Jawaban:

208

Json.Net lebih suka menggunakan konstruktor default (parameterless) pada objek jika ada. Jika ada beberapa konstruktor dan Anda ingin Json.Net menggunakan yang non-default, maka Anda dapat menambahkan [JsonConstructor]atribut ke konstruktor yang Anda ingin Json.Net panggil.

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

Adalah penting bahwa nama parameter konstruktor cocok dengan nama properti yang sesuai dari objek JSON (mengabaikan kasus) agar ini berfungsi dengan benar. Namun, Anda tidak harus memiliki parameter konstruktor untuk setiap properti objek. Untuk properti objek JSON yang tidak tercakup oleh parameter konstruktor, Json.Net akan mencoba menggunakan pengakses properti publik (atau properti / bidang yang ditandai dengan [JsonProperty]) untuk mengisi objek setelah membangunnya.

Jika Anda tidak ingin menambahkan atribut ke kelas Anda atau tidak mengontrol kode sumber untuk kelas yang Anda coba deserialize, maka alternatif lain adalah membuat JsonConverter kustom untuk instantiate dan mengisi objek Anda. Sebagai contoh:

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

Kemudian, tambahkan konverter ke pengaturan serializer Anda, dan gunakan pengaturan ketika Anda deserialize:

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Result result = JsonConvert.DeserializeObject<Result>(jsontext, settings);
Brian Rogers
sumber
4
Ini berhasil. Agak menyebalkan bahwa saya sekarang harus mengambil ketergantungan JSON.net dalam proyek model saya, tapi apa hei. Saya akan menandai ini sebagai jawabannya.
kmacdonald
3
Ada opsi lain - Anda bisa membuat kustom JsonConverteruntuk kelas Anda. Ini akan menghapus ketergantungan, tetapi Anda harus menangani instantiating dan mengisi sendiri objek di konverter. Mungkin juga untuk menulis sebuah kebiasaan ContractResolveryang akan mengarahkan Json.Net untuk menggunakan konstruktor lain dengan mengubah JsonObjectContract, tetapi ini bisa terbukti sedikit lebih rumit daripada kedengarannya.
Brian Rogers
Ya, saya pikir atributnya akan berfungsi dengan baik. Panggilan deserialize sebenarnya generik sehingga bisa berupa semua jenis objek. Saya pikir jawaban awal Anda akan bekerja dengan baik. terimakasih atas infonya!
kmacdonald
2
Akan sangat membantu jika dimungkinkan untuk menetapkan konvensi lain untuk pemilihan konstruktor. Sebagai contoh, saya pikir wadah Unity mendukung ini. Kemudian Anda bisa membuatnya sehingga selalu memilih konstruktor dengan sebagian besar parameter alih-alih kembali ke default. Adakah kemungkinan titik ekstensi seperti itu ada di Json.Net?
julealgon
1
Jangan lupausing Newtonsoft.Json;
Bruno Bieri
36

Agak terlambat dan tidak cocok di sini, tapi saya akan menambahkan solusi saya di sini, karena pertanyaan saya telah ditutup sebagai duplikat dari yang ini, dan karena solusi ini sangat berbeda.

Saya membutuhkan cara umum untuk menginstruksikan Json.NETuntuk memilih konstruktor yang paling spesifik untuk tipe struct yang ditentukan pengguna, jadi saya dapat menghilangkan JsonConstructoratribut yang akan menambah ketergantungan pada proyek di mana setiap struct tersebut didefinisikan.

Saya telah sedikit merekayasa balik dan menerapkan penyelesaian kontrak khusus tempat saya mengganti CreateObjectContractmetode untuk menambahkan logika kreasi khusus saya.

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

Saya menggunakannya seperti ini.

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");
Zoltán Tamási
sumber
2
Saat ini saya menggunakan jawaban yang diterima di atas, tetapi ingin mengucapkan terima kasih karena telah menunjukkan solusi Anda juga!
DotBert
1
Saya menghapus batasan pada struct (cek untuk objectType.IsValueType) dan ini bekerja dengan baik, terima kasih!
Alex Angas
@AlexAngas Ya menerapkan strategi ini secara umum memang masuk akal terima kasih atas tanggapan Anda.
Zoltán Tamási
3

Berdasarkan beberapa jawaban di sini, saya telah menulis CustomConstructorResolveruntuk digunakan dalam proyek saat ini, dan saya pikir itu mungkin membantu orang lain.

Ini mendukung mekanisme resolusi berikut, semua dapat dikonfigurasi:

  • Pilih satu konstruktor pribadi sehingga Anda dapat menentukan satu konstruktor pribadi tanpa harus menandainya dengan atribut.
  • Pilih konstruktor pribadi yang paling spesifik sehingga Anda dapat memiliki beberapa kelebihan, masih tanpa harus menggunakan atribut.
  • Pilih konstruktor yang ditandai dengan atribut nama tertentu - seperti resolver default, tetapi tanpa ketergantungan pada paket Json.Net karena Anda perlu referensi 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;
    }
}

Berikut ini adalah versi lengkap dengan dokumentasi XML sebagai intisari: https://gist.github.com/maverickelementalch/80f77f4b6bdce3b434b0f7a1d06baa95

Umpan balik dihargai.

Björn Jarisch
sumber
Solusi bagus! Terima kasih telah berbagi.
thomai
1

Perilaku default Newtonsoft.Json akan menemukan publickonstruktor. Jika konstruktor default Anda hanya digunakan dalam berisi kelas atau rakitan yang sama, Anda dapat mengurangi tingkat akses ke protectedatau internalsehingga Newtonsoft.Json akan memilih publickonstruktor yang Anda inginkan .

Diakui, solusi ini agak sangat terbatas pada kasus-kasus tertentu.

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;
}
Darkato
sumber
-1

Larutan:

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

Model:

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