JSON.net: Varsayılan kurucuyu kullanmadan seri durumdan nasıl çıkarılır?


136

Varsayılan bir kurucuya ve ayrıca bir dizi parametre alan aşırı yüklenmiş bir kurucuya sahip bir sınıfım var. Bu parametreler nesnedeki alanlarla eşleşir ve yapım sırasında atanır. Bu noktada, başka amaçlar için varsayılan kurucuya ihtiyacım var, bu yüzden eğer yapabiliyorsam onu ​​korumak istiyorum.

Benim Sorunum: Varsayılan kurucuyu kaldırır ve JSON dizesini geçirirsem, nesne doğru şekilde seriyi kaldırır ve herhangi bir sorun olmadan yapıcı parametrelerine geçer. Sonunda beklediğim şekilde nesneyi geri alıyorum. Ancak, varsayılan kurucuyu nesneye ekler koymaz JsonConvert.DeserializeObject<Result>(jsontext), özellikleri aradığımda artık doldurulmuyor.

Bu noktada new JsonSerializerSettings(){CheckAdditionalContent = true}seriyi kaldırma çağrısına eklemeye çalıştım . bu hiçbir şey yapmadı.

Başka bir not. yapılandırıcı parametreleri, parametrelerin küçük harfle başlaması dışında alanların adlarıyla tam olarak eşleşir. Bahsettiğim gibi seriyi kaldırma varsayılan kurucu olmadan iyi çalıştığı için bunun önemli olacağını düşünmüyorum.

İşte kurucularımın bir örneği:

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


Yanıtlar:


209

Json.Net, varsa bir nesne üzerinde varsayılan (parametresiz) kurucuyu kullanmayı tercih eder. Birden fazla kurucu varsa ve Json.Net'in varsayılan olmayan bir tane kullanmasını istiyorsanız, Json.Net'in çağırmasını istediğiniz kurucuya [JsonConstructor]özniteliği ekleyebilirsiniz .

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

Bunun doğru çalışması için yapıcı parametre adlarının JSON nesnesinin karşılık gelen özellik adlarıyla eşleşmesi (büyük / küçük harfe bakılmaksızın) önemlidir. Bununla birlikte, nesnenin her özelliği için mutlaka bir yapıcı parametresine sahip olmanız gerekmez. Yapıcı parametreleri tarafından kapsanmayan JSON nesnesi özellikleri için, Json.Net [JsonProperty]nesneyi oluşturduktan sonra doldurmak için genel özellik erişimcilerini (veya ile işaretlenmiş özellikleri / alanları ) kullanmaya çalışacaktır .

Sınıfınıza öznitelik eklemek istemiyorsanız veya serisini kaldırmaya çalıştığınız sınıfın kaynak kodunu başka bir şekilde kontrol etmiyorsanız, başka bir alternatif, nesnenizi başlatmak ve doldurmak için özel bir JsonConverter oluşturmaktır. Örneğin:

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

Ardından, dönüştürücüyü serileştirici ayarlarınıza ekleyin ve serisini kaldırırken ayarları kullanın:

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

4
Bu işe yaradı. Şimdi modeller projemde JSON.net bağımlılığını almak zorunda olmam berbat bir şey, ama hey. Bunu cevap olarak işaretleyeceğim.
kmacdonald

3
Başka seçenekler de var - JsonConvertersınıfınız için bir özel oluşturabilirsiniz . Bu, bağımlılığı ortadan kaldırır, ancak daha sonra dönüştürücünün içinde nesneyi kendiniz örneklemeyi ve doldurmayı halletmeniz gerekir. ContractResolverJson.Net'i diğer kurucuyu değiştirerek kullanması için yönlendirecek bir özel yazmak da mümkün olabilir JsonObjectContract, ancak bu göründüğünden biraz daha zor olabilir.
Brian Rogers

Evet, özniteliğin iyi çalışacağını düşünüyorum. Seri durumdan çıkarma çağrısı aslında geneldir, böylece herhangi bir nesne türü olabilir. Orijinal cevabınızın işe yarayacağını düşünüyorum. bilgi için teşekkürler!
kmacdonald

2
Yapıcı seçimi için başka bir kongre ayarlamak mümkün olsaydı gerçekten yardımcı olurdu. Örneğin, Unity konteynerinin bunu desteklediğini düşünüyorum. Daha sonra, varsayılan olana geri dönmek yerine her zaman çoğu parametreye sahip kurucuyu seçecek şekilde yapabilirsiniz. Json.Net'te böyle bir uzantı noktasının bulunma olasılığı var mı?
julealgon

1
Unutmausing Newtonsoft.Json;
Bruno Bieri

36

Biraz geç ve buraya tam olarak uymuyor, ama çözümümü buraya ekleyeceğim, çünkü sorum bunun bir kopyası olarak kapatılmıştı ve çünkü bu çözüm tamamen farklı.

Json.NETKullanıcı tanımlı yapı türü için en spesifik kurucuyu tercih etme talimatı vermek için genel bir yola ihtiyacım vardı , böylece JsonConstructorher bir yapının tanımlandığı projeye bir bağımlılık ekleyecek nitelikleri atlayabilirim .

Biraz tersine mühendislik CreateObjectContractyaptım ve özel oluşturma mantığımı eklemek için yöntemi geçersiz kıldığım özel bir sözleşme çözümleyici uyguladım .

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

Ben böyle kullanıyorum.

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
Şu anda yukarıdaki kabul edilen cevabı kullanıyorum, ancak çözümünüzü de gösterdiğiniz için teşekkür etmek istiyorum!
DotBert

1
Yapılar üzerindeki kısıtlamayı kaldırdım (kontrol için objectType.IsValueType) ve bu harika çalışıyor, teşekkürler!
Alex Angas

@AlexAngas Evet, bu stratejiyi genel olarak uygulamak mantıklı, geri bildiriminiz için teşekkür ederiz.
Zoltán Tamási

3

Buradaki bazı cevaplara dayanarak, CustomConstructorResolvermevcut bir projede kullanmak için bir tane yazdım ve başka birine yardımcı olabileceğini düşündüm.

Aşağıdaki çözünürlük mekanizmalarını destekler ve tümü yapılandırılabilir:

  • Tek bir özel kurucu seçin, böylece onu bir öznitelikle işaretlemek zorunda kalmadan bir özel kurucu tanımlayabilirsiniz.
  • En spesifik özel kurucuyu seçin, böylece öznitelikleri kullanmak zorunda kalmadan birden çok aşırı yüklemeye sahip olabilirsiniz.
  • Belli bir ismin özniteliğiyle işaretlenmiş yapıcıyı seçin - varsayılan çözümleyici gibi, ancak başvurmanız gerektiğinden Json.Net paketine bağımlılık olmadan 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;
    }
}

Özet olarak XML belgelerini içeren tam sürüm: https://gist.github.com/maverickelementalch/80f77f4b6bdce3b434b0f7a1d06baa95

Geri bildirim takdir edildi.


Harika çözüm! Paylaşım için teşekkürler.
thomai

1

Newtonsoft.Json'ın varsayılan davranışı yapıcıları bulacaktır public. Varsayılan kurucunuz yalnızca içeren sınıfta veya aynı derlemede kullanılıyorsa, erişim düzeyini protectedveya internalöyle azaltabilirsiniz ki Newtonsoft.Json istediğiniz kurucuyu seçecektir public.

Kuşkusuz, bu çözüm özel durumlarla oldukça sınırlıdır.

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

Çözüm:

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; }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.