JSON.NET'te seriyi kaldırma için döküm arayüzleri


129

Çeşitli web sitelerinden JSON nesnelerini alacak (bilgi kazımayı düşünün) ve bunları C # nesnelerine çevirecek bir okuyucu kurmaya çalışıyorum. Şu anda seriyi kaldırma işlemi için JSON.NET kullanıyorum. Karşılaştığım sorun, bir sınıftaki arayüz seviyesi özelliklerinin nasıl işleneceğini bilmemesidir. Yani doğayla ilgili bir şey:

public IThingy Thing

Hatayı üretecek:

IThingy türünde bir örnek oluşturulamadı. Tür, bir arabirim veya soyut bir sınıftır ve somutlaştırılamaz.

Üzerinde çalıştığım kod hassas kabul edildiğinden ve birim testi çok önemli olduğundan, bir Thingy'nin aksine bir IThingy olması nispeten önemlidir. Thingy gibi tam teşekküllü nesnelerle atomik test komut dosyaları için nesnelerin alay edilmesi mümkün değildir. Bir arayüz olmalılar.

Bir süredir JSON.NET'in belgelerini inceliyorum ve bu sitede bununla ilgili bulabildiğim sorular bir yıldan uzun bir süre öncesine ait. Herhangi bir yardım?

Ayrıca, önemliyse uygulamam .NET 4.0'da yazılmıştır.


Yanıtlar:


115

@SamualDavis , burada özetleyeceğim ilgili bir soruda harika bir çözüm sağladı .

Bir JSON akışını, arabirim özelliklerine sahip somut bir sınıfa kaldırmanız gerekiyorsa, somut sınıfları sınıf için bir yapıcıya parametreler olarak dahil edebilirsiniz ! NewtonSoft seriyi kaldırma özelliği, özelliklerin serisini kaldırmak için bu somut sınıfları kullanması gerektiğini anlayacak kadar akıllıdır.

İşte bir örnek:

public class Visit : IVisit
{
    /// <summary>
    /// This constructor is required for the JSON deserializer to be able
    /// to identify concrete classes to use when deserializing the interface properties.
    /// </summary>
    public Visit(MyLocation location, Guest guest)
    {
        Location = location;
        Guest = guest;
    }
    public long VisitId { get; set; }
    public ILocation Location { get;  set; }
    public DateTime VisitDate { get; set; }
    public IGuest Guest { get; set; }
}

15
Bu bir ICollection ile nasıl çalışır? ICollection <IGuest> Konuklar {get; set;}
DrSammyD

12
ICollection <ConcreteClass> ile çalışır, bu nedenle ICollection <Guest> çalışır. Bilginize olduğu gibi, [JsonConstructor] özelliğini kurucunuza koyabilirsiniz, böylece birden fazla
kurucunuz

6
Aynı soruna takılı kaldım, benim durumumda arabirimin birkaç uygulamasına sahibim (örneğinizde arabirim ILocation) öyleyse ya MyLocation, VIPLocation, OrdinaryLocation gibi sınıflar varsa. Bunları Konum özelliğine nasıl eşleyebilirim? MyLocation gibi tek bir uygulamanız varsa bu kolaydır, ancak ILocation'ın birden fazla uygulaması varsa bunu nasıl yapacaksınız?
Ather

10
Birden fazla kurucunuz varsa, özel kurucunuzu [JsonConstructor]niteliği ile işaretleyebilirsiniz .
Dr Rob Lang

26
Bu hiç iyi değil. Arabirimleri kullanmanın amacı, bağımlılık enjeksiyonu kullanmaktır, ancak bunu yapıcınız tarafından istenen nesne türü bir parametre ile yapmak, bir özellik olarak bir arabirime sahip olma noktasını tamamen alt üst edersiniz.
Jérôme MEVEL

57

( Bu sorudan kopyalandı )

Gelen JSON üzerinde kontrole sahip olmadığım durumlarda (ve dolayısıyla bunun bir $ type özelliği içerdiğinden emin olamadığım) durumlarda, somut türü açıkça belirtmenize izin veren özel bir dönüştürücü yazdım:

public class Model
{
    [JsonConverter(typeof(ConcreteTypeConverter<Something>))]
    public ISomething TheThing { get; set; }
}

Bu sadece somut türü açıkça belirtirken Json.Net'teki varsayılan serileştirici uygulamasını kullanır.

Bu blog gönderisinde bir genel bakış mevcuttur . Kaynak kodu aşağıdadır:

public class ConcreteTypeConverter<TConcrete> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        //assume we can convert to anything for now
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        //explicitly specify the concrete type we want to create
        return serializer.Deserialize<TConcrete>(reader);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        //use the default serialization - it works fine
        serializer.Serialize(writer, value);
    }
}

11
Bu yaklaşımı gerçekten beğendim ve kendi projemize uyguladım. Hatta ConcreteListTypeConverter<TInterface, TImplementation>türündeki sınıf üyelerini işlemek için bir bile ekledim IList<TInterface>.
Oliver

3
Bu harika bir kod parçası. Yine concreteTypeConverterde soruda gerçek koda sahip olmak daha güzel olabilir .
Chris

2
@Oliver - Uygulamanızı yayınlayabilir misiniz ConcreteListTypeConverter<TInterface, TImplementation>?
Michael

2
Ve eğer ISomething'in iki uygulayıcısına sahipseniz?
bdaniel7

56

Neden dönüştürücü kullanıyorsunuz? Newtonsoft.JsonBu sorunu tam olarak çözmek için yerel bir işlevsellik vardır :

Set TypeNameHandlingiçinde JsonSerializerSettingshiçTypeNameHandling.Auto

JsonConvert.SerializeObject(
  toSerialize,
  new JsonSerializerSettings()
  {
    TypeNameHandling = TypeNameHandling.Auto
  });

Bu, bir türün somut bir örneği olarak değil, bir arabirim veya soyut bir sınıf olarak tutulan her türü json'a koyacaktır.

Serileştirme ve seriyi kaldırma için aynı ayarları kullandığınızdan emin olun .

Test ettim ve listelerde bile bir cazibe gibi çalışıyor.

Site bağlantılarını içeren Arama Sonuçları Web sonucu

⚠️ UYARI :

Bunu yalnızca bilinen ve güvenilir bir kaynaktan json için kullanın. Kullanıcı snipsnipsnip , bunun gerçekten bir vunerability olduğunu doğru bir şekilde belirtti.

Daha fazla bilgi için CA2328 ve SCS0028'e bakın .


Kaynak ve alternatif bir manuel uygulama: Code Inside Blog


3
Mükemmel, bu hızlı ve kirli bir derin klon için bana yardımcı oldu ( stackoverflow.com/questions/78536/deep-cloning-objects )
Compufreak

1
@Shimmy Nesneleri: "JSON nesne yapısına serileştirirken .NET türü adını ekleyin." Otomatik: Serileştirilen nesnenin türü, bildirilen türüyle aynı olmadığında .NET türü adını ekleyin. Bunun varsayılan olarak kök serileştirilmiş nesneyi içermediğini unutmayın. Kök nesnenin tür adını JSON'a dahil etmek için SerializeObject (Object, Type, JsonSerializerSettings) veya Serialize (JsonWriter, Object, Type) ile bir kök türü nesnesi belirtmelisiniz. "Kaynak: newtonsoft.com/json/help/html/…
Mafii

4
Bunu Deserialization'da denedim ve işe yaramıyor. Bu Yığın Taşması sorusunun konu satırı, "JSON.NET'te serileştirme için ara yüzleri döküm"
Justin Russo

3
@JustinRusso yalnızca json aynı ayar ile serileştirildiğinde çalışır
Mafii

3
Kirli değilse hızlı çözüm için oy verin. Yalnızca konfigürasyonları serileştiriyorsanız, bu çalışır. Dönüştürücüler inşa etmek için geliştirmeyi durdurur ve enjekte edilen her mülkün dekorasyonunu kesinlikle yener. serializer.TypeNameHandling = TypeNameHandling.Auto; JsonConvert.DefaultSettings (). TypeNameHandling = TypeNameHandling.Auto;
Sean Anderson

39

Birden çok arabirim uygulamasının serisini kaldırmayı etkinleştirmek için JsonConverter'ı kullanabilirsiniz, ancak bir öznitelik yoluyla kullanamazsınız:

Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Converters.Add(new DTOJsonConverter());
Interfaces.IEntity entity = serializer.Deserialize(jsonReader);

DTOJsonConverter, her arabirimi somut bir uygulama ile eşler:

class DTOJsonConverter : Newtonsoft.Json.JsonConverter
{
    private static readonly string ISCALAR_FULLNAME = typeof(Interfaces.IScalar).FullName;
    private static readonly string IENTITY_FULLNAME = typeof(Interfaces.IEntity).FullName;


    public override bool CanConvert(Type objectType)
    {
        if (objectType.FullName == ISCALAR_FULLNAME
            || objectType.FullName == IENTITY_FULLNAME)
        {
            return true;
        }
        return false;
    }

    public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        if (objectType.FullName == ISCALAR_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientScalar));
        else if (objectType.FullName == IENTITY_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientEntity));

        throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
    }

    public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

DTOJsonConverter yalnızca seriyi kaldırıcı için gereklidir. Serileştirme süreci değişmez. Json nesnesinin somut tür adlarını yerleştirmesine gerek yoktur.

Bu SO gönderisi aynı çözümü jenerik bir JsonConverter ile bir adım daha ileri götürür.


Bu WriteJson yönteminin serileştirici.Serialize çağrısı bir yığın taşmasına neden olmaz mıydı, çünkü dönüştürücü tarafından serileştirilen değerde serileştirmeyi çağırmak, dönüştürücünün WriteJson yönteminin yinelemeli olarak yeniden çağrılmasına neden olur mu?
Triynko

CanConvert () yöntemi tutarlı bir sonuç döndürürse olmamalıdır.
Eric Boumendil

3
FullNameTürleri doğrudan karşılaştırmak varken neden ' leri karşılaştırıyorsunuz?
Alex Zhukovskiy

Sadece türleri karşılaştırmak da sorun değil.
Eric Boumendil

23

Soyut türü gerçek türe eşlemek için bu sınıfı kullanın:

public class AbstractConverter<TReal, TAbstract> : JsonConverter where TReal : TAbstract
{
    public override Boolean CanConvert(Type objectType) 
        => objectType == typeof(TAbstract);

    public override Object ReadJson(JsonReader reader, Type type, Object value, JsonSerializer jser) 
        => jser.Deserialize<TReal>(reader);

    public override void WriteJson(JsonWriter writer, Object value, JsonSerializer jser) 
        => jser.Serialize(writer, value);
}

... ve seri durumdan çıkarıldığında:

        var settings = new JsonSerializerSettings
        {
            Converters = {
                new AbstractConverter<Thing, IThingy>(),
                new AbstractConverter<Thing2, IThingy2>()
            },
        };

        JsonConvert.DeserializeObject(json, type, settings);

1
Sorunumu çözen güzel ve özlü bir cevabı gerçekten seviyorum. Oto yüze falan gerek yok!
Ben Power

3
Bunu dönüştürücü sınıf bildirimine where TReal : TAbstract
eklemeye değer:

1
Daha eksiksiz bir yer olabilir where TReal : class, TAbstract, new().
Erik Philips

2
Bu dönüştürücüyü struct ile de kullandım, "burada TReal: TAbstract" yeterli olacaktır diye düşünüyorum. Hepinize teşekkürler.
Gildor

2
Altın! Gitmenin şık yolu.
SwissCoder

12

Nicholas Westby harika bir makalede harika bir çözüm sundu .

JSON'un bunun gibi bir arabirim uygulayan birçok olası sınıftan birine serisini kaldırmasını istiyorsanız:

public class Person
{
    public IProfession Profession { get; set; }
}

public interface IProfession
{
    string JobTitle { get; }
}

public class Programming : IProfession
{
    public string JobTitle => "Software Developer";
    public string FavoriteLanguage { get; set; }
}

public class Writing : IProfession
{
    public string JobTitle => "Copywriter";
    public string FavoriteWord { get; set; }
}

public class Samples
{
    public static Person GetProgrammer()
    {
        return new Person()
        {
            Profession = new Programming()
            {
                FavoriteLanguage = "C#"
            }
        };
    }
}

Özel bir JSON dönüştürücü kullanabilirsiniz:

public class ProfessionConverter : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanRead => true;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(IProfession);
    }
    public override void WriteJson(JsonWriter writer,
        object value, JsonSerializer serializer)
    {
        throw new InvalidOperationException("Use default serialization.");
    }

    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var profession = default(IProfession);
        switch (jsonObject["JobTitle"].Value())
        {
            case "Software Developer":
                profession = new Programming();
                break;
            case "Copywriter":
                profession = new Writing();
                break;
        }
        serializer.Populate(jsonObject.CreateReader(), profession);
        return profession;
    }
}

Ayrıca, özel dönüştürücünüzü kullanacağını bildirmek için "Meslek" özelliğini bir JsonConverter niteliğiyle dekore etmeniz gerekecektir:

    public class Person
    {
        [JsonConverter(typeof(ProfessionConverter))]
        public IProfession Profession { get; set; }
    }

Ve sonra, sınıfınızı bir Arayüzle yayınlayabilirsiniz:

Person person = JsonConvert.DeserializeObject<Person>(jsonString);

8

Deneyebileceğiniz iki şey:

Bir dene / ayrıştır modeli uygulayın:

public class Organisation {
  public string Name { get; set; }

  [JsonConverter(typeof(RichDudeConverter))]
  public IPerson Owner { get; set; }
}

public interface IPerson {
  string Name { get; set; }
}

public class Tycoon : IPerson {
  public string Name { get; set; }
}

public class Magnate : IPerson {
  public string Name { get; set; }
  public string IndustryName { get; set; }
}

public class Heir: IPerson {
  public string Name { get; set; }
  public IPerson Benefactor { get; set; }
}

public class RichDudeConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return (objectType == typeof(IPerson));
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    // pseudo-code
    object richDude = serializer.Deserialize<Heir>(reader);

    if (richDude == null)
    {
        richDude = serializer.Deserialize<Magnate>(reader);
    }

    if (richDude == null)
    {
        richDude = serializer.Deserialize<Tycoon>(reader);
    }

    return richDude;
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  }
}

Veya, nesne modelinizde bunu yapabiliyorsanız, IPerson ve yaprak nesneleriniz arasında somut bir temel sınıf uygulayın ve buna göre seriyi kaldırın.

Birincisi çalışma zamanında potansiyel olarak başarısız olabilir, ikincisi nesne modelinizde değişiklik yapılmasını gerektirir ve çıktıyı en düşük ortak paydaya homojenleştirir.


Çalışmam gereken ölçek nedeniyle bir dene / ayrıştır modeli mümkün değil. Çok fazla gerçekleşen gömülü JSON nesnelerini temsil etmek için yüzlerce saplama / yardımcı nesne içeren yüzlerce temel nesnenin kapsamını düşünmem gerekiyor. Nesne modelini değiştirmek söz konusu değil, ancak özelliklerde somut bir temel sınıf kullanmak, birim testi için öğelerle dalga geçmemize neden olmaz mı? Yoksa bir şekilde geri mi alıyorum?
tmesser

Yine de IPerson'dan bir alay uygulayabilirsiniz - Organisation.Owner özelliğinin türünün hala IPerson olduğunu unutmayın. Ancak keyfi bir hedefin serisini kaldırmak için somut bir tür döndürmeniz gerekir. Tür tanımına sahip değilseniz ve kodunuzun gerektireceği minimum özellik kümesini tanımlayamıyorsanız, son başvurunuz bir anahtar / değer çantası gibi bir şeydir. Facebook örnek yorumunuzu kullanarak - ILocation (bir veya daha fazla) uygulamalarınızın neye benzediğini bir yanıt olarak gönderebilir misiniz? Bu, işleri ilerletmeye yardımcı olabilir.
mcw

Birincil umut alay etmek olduğundan, ILocation arayüzü gerçekten, Konum somut nesnesi için sadece bir cephedir. Sadece yukarı çalıştı Hızlı bir örnek bu (gibi bir şey olurdu pastebin.com/mWQtqGnB arayüzü ve bu (için) pastebin.com/TdJ6cqWV somut bir nesne için).
tmesser

Ve bir sonraki adıma geçmek için bu, IPage'in nasıl görüneceğinin bir örneğidir ( pastebin.com/iuGifQXp ) ve Page ( pastebin.com/ebqLxzvm ). Sorun, elbette, Sayfanın serileştirilmesinin kaldırılması genellikle iyi çalışsa da, ILocation özelliğine ulaştığında boğulacak olmasıdır.
tmesser

Tamam, gerçekten kazandığınız ve serisini kaldırdığınız nesneler hakkında düşününce - genel olarak JSON verilerinin tek bir somut sınıf tanımıyla tutarlı olması durumu mudur? Anlamı (varsayımsal olarak), dizilişi kaldırılmış nesne için Konum'u somut tip olarak kullanmaya uygun olmayan ek özelliklere sahip "konumlar" ile karşılaşmaz mısınız? Öyleyse, Sayfanın ILocation özelliğini bir "LocationConverter" ile ilişkilendirmek işe yarayacaktır. Değilse ve bunun nedeni JSON verilerinin her zaman katı veya tutarlı bir yapıya (ILocation gibi) uymaması, o zaman (... devam)
mcw

8

Bunu yararlı buldum. Sen de yapabilirsin.

Örnek Kullanım

public class Parent
{
    [JsonConverter(typeof(InterfaceConverter<IChildModel, ChildModel>))]
    IChildModel Child { get; set; }
}

Özel Oluşturma Dönüştürücü

public class InterfaceConverter<TInterface, TConcrete> : CustomCreationConverter<TInterface>
    where TConcrete : TInterface, new()
{
    public override TInterface Create(Type objectType)
    {
        return new TConcrete();
    }
}

Json.NET belgeleri


1
Uygulanabilir bir çözüm değil. Listeleri ele almaz ve her yerde serpme dekoratörlere / ek açıklamalara yol açar.
Sean Anderson

5

Oliver tarafından atıfta bulunulan ConcreteListTypeConverter'ı merak edenler için işte benim girişim:

public class ConcreteListTypeConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface 
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var res = serializer.Deserialize<List<TImplementation>>(reader);
        return res.ConvertAll(x => (TInterface) x);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

1
Geçersiz kılınanla kafam karıştı CanConvert(Type objectType) { return true;}. Keskin görünüyor, bu tam olarak nasıl yardımcı oluyor? Yanılıyor olabilirim ama bu, daha küçük deneyimsiz bir dövüşçüye rakibi ne olursa olsun savaşı kazanacaklarını söylemek gibi değil mi?
Chef_Code

4

Ne olursa olsun, bunu çoğunlukla kendim halletmek zorunda kaldım. Her nesnenin bir Deserialize (string jsonStream) yöntemi vardır. Birkaç pasajı:

JObject parsedJson = this.ParseJson(jsonStream);
object thingyObjectJson = (object)parsedJson["thing"];
this.Thing = new Thingy(Convert.ToString(thingyObjectJson));

Bu durumda, new Thingy (string) , uygun somut tipin Deserialize (string jsonStream) yöntemini çağıracak bir yapıcıdır . Bu şema, json.NET'in idare edebileceği temel noktalara ulaşıncaya kadar aşağı ve aşağı gitmeye devam edecektir.

this.Name = (string)parsedJson["name"];
this.CreatedTime = DateTime.Parse((string)parsedJson["created_time"]);

Öyleyse ve benzeri. Bu kurulum, kütüphanenin büyük bir bölümünü yeniden düzenlemek zorunda kalmadan veya dahil olan nesnelerin sayısı nedeniyle tüm kitaplığımızı tıkanacak olan hantal deneme / ayrıştırma modellerini kullanmak zorunda kalmadan yönetebileceği json.NET kurulumlarını sağlamamı sağladı. Aynı zamanda, belirli bir nesnede herhangi bir json değişikliğini etkin bir şekilde idare edebileceğim ve nesnenin dokunduğu her şey için endişelenmeme gerek olmadığı anlamına gelir. Bu kesinlikle ideal bir çözüm değildir, ancak ünitemizden ve entegrasyon testimizden oldukça iyi çalışır.


4

Aşağıdaki gibi bir otomatik yüz ayarını varsayalım:

public class AutofacContractResolver : DefaultContractResolver
{
    private readonly IContainer _container;

    public AutofacContractResolver(IContainer container)
    {
        _container = container;
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);

        // use Autofac to create types that have been registered with it
        if (_container.IsRegistered(objectType))
        {
           contract.DefaultCreator = () => _container.Resolve(objectType);
        }  

        return contract;
    }
}

Öyleyse, sınıfınızın şöyle olduğunu varsayalım:

public class TaskController
{
    private readonly ITaskRepository _repository;
    private readonly ILogger _logger;

    public TaskController(ITaskRepository repository, ILogger logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public ITaskRepository Repository
    {
        get { return _repository; }
    }

    public ILogger Logger
    {
        get { return _logger; }
    }
}

Bu nedenle, seriyi kaldırma işleminde çözücünün kullanımı şöyle olabilir:

ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<TaskRepository>().As<ITaskRepository>();
builder.RegisterType<TaskController>();
builder.Register(c => new LogService(new DateTime(2000, 12, 12))).As<ILogger>();

IContainer container = builder.Build();

AutofacContractResolver contractResolver = new AutofacContractResolver(container);

string json = @"{
      'Logger': {
        'Level':'Debug'
      }
}";

// ITaskRespository and ILogger constructor parameters are injected by Autofac 
TaskController controller = JsonConvert.DeserializeObject<TaskController>(json, new JsonSerializerSettings
{
    ContractResolver = contractResolver
});

Console.WriteLine(controller.Repository.GetType().Name);

Daha fazla ayrıntıyı http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm adresinde görebilirsiniz.


Bunu en iyi çözüm olarak değerlendireceğim. DI, bu günlerde c # web geliştiricileri tarafından çok yaygın bir şekilde kullanılmaktadır ve bu, çözümleyici tarafından bu tür dönüştürmeyi idare etmek için merkezi bir yer olarak oldukça uygundur.
appletwo

3

Hiçbir nesne olacak hiç olmak arayüzleri tüm tanım gereği soyut olarak bir IThingy.

Bu ilk tefrika edilmiş olan nesne bazılarının oldu beton uygulanması, tip soyut bir arayüz. Aynı betona sahip olmalısın sınıfa serileştirilmiş verileri canlandırmanız gerekir.

Ortaya çıkan nesne daha sonra bazı tip olacaktır uygular soyut arayüzü arıyoruz.

Gönderen belgelere Eğer kullanabileceğiniz izler

(Thingy)JsonConvert.DeserializeObject(jsonString, typeof(Thingy));

JSON.NET'i somut tür hakkında bilgilendirmek için seriyi kaldırırken.


Bu tam olarak bir yıl öncesinden bahsettiğim yazı. Tek önemli öneri (özel dönüştürücüler yazmak), dikkate almaya zorlandığım ölçekle çok da uygulanabilir değil. JSON.NET, geçen yıl çok değişti. Bir sınıf ve bir arabirim arasındaki ayrımı mükemmel bir şekilde anlıyorum, ancak C # aynı zamanda bir arabirimden yazarak arabirimi uygulayan bir nesneye örtük dönüştürmeleri de destekler. Esasen JSON.NET'e bu arayüzü hangi nesnenin uygulayacağını söylemenin bir yolu olup olmadığını soruyorum.
tmesser

Size gösterdiğim cevapta hepsi oradaydı. _typeKullanılacak somut türü işaret eden bir özellik olduğundan emin olun .
Sean Kinsey

Ve C # 'ın, arayüz olarak bildirilen bir değişkenden somut bir türe herhangi bir ipucu olmaksızın her türlü' örtük 'tiplendirmeyi desteklediğinden kesinlikle şüpheliyim.
Sean Kinsey

Yanlış okumadıysam, _type özelliğinin serileştirilecek JSON'da olması gerekiyordu. Bu, yalnızca halihazırda serileştirdiğiniz şeyin serisini kaldırırsanız iyi çalışır, ancak burada olan bu değildir. JSON'u bu standardı takip etmeyecek birkaç siteden alıyorum.
tmesser

@YYY - JSON kaynağına hem serileştirmeyi hem de seriyi kaldırma işlemini kontrol ediyor musunuz? Çünkü nihayetinde somut türü seriyi kaldırırken kullanmak için bir ipucu olarak serileştirilmiş JSON'a yerleştirmeniz gerekecek veya çalışma zamanında somut türü algılayan / tespit etmeye çalışan bir tür deneme / ayrıştırma modeli kullanmanız gerekecektir. ve uygun seriyi kaldırıcıyı çağırın.
mcw

3

Güzel bir şekilde genel olduğu için beğendiğim bu soruna çözümüm şu şekilde:

/// <summary>
/// Automagically convert known interfaces to (specific) concrete classes on deserialisation
/// </summary>
public class WithMocksJsonConverter : JsonConverter
{
    /// <summary>
    /// The interfaces I know how to instantiate mapped to the classes with which I shall instantiate them, as a Dictionary.
    /// </summary>
    private readonly Dictionary<Type,Type> conversions = new Dictionary<Type,Type>() { 
        { typeof(IOne), typeof(MockOne) },
        { typeof(ITwo), typeof(MockTwo) },
        { typeof(IThree), typeof(MockThree) },
        { typeof(IFour), typeof(MockFour) }
    };

    /// <summary>
    /// Can I convert an object of this type?
    /// </summary>
    /// <param name="objectType">The type under consideration</param>
    /// <returns>True if I can convert the type under consideration, else false.</returns>
    public override bool CanConvert(Type objectType)
    {
        return conversions.Keys.Contains(objectType);
    }

    /// <summary>
    /// Attempt to read an object of the specified type from this reader.
    /// </summary>
    /// <param name="reader">The reader from which I read.</param>
    /// <param name="objectType">The type of object I'm trying to read, anticipated to be one I can convert.</param>
    /// <param name="existingValue">The existing value of the object being read.</param>
    /// <param name="serializer">The serializer invoking this request.</param>
    /// <returns>An object of the type into which I convert the specified objectType.</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            return serializer.Deserialize(reader, this.conversions[objectType]);
        }
        catch (Exception)
        {
            throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
        }
    }

    /// <summary>
    /// Not yet implemented.
    /// </summary>
    /// <param name="writer">The writer to which I would write.</param>
    /// <param name="value">The value I am attempting to write.</param>
    /// <param name="serializer">the serializer invoking this request.</param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

}

Açıkça ve önemsiz bir şekilde, dönüştürme örneği değişkenini somutlaştırmak için Dictionary <Type, Type> türünde bir argüman alan bir kurucu ekleyerek onu daha genel bir dönüştürücüye dönüştürebilirsiniz.


3

Birkaç yıl geçti ve benzer bir sorun yaşadım. Benim durumumda, yoğun bir şekilde iç içe geçmiş arayüzler ve genel bir sınıfla çalışacak şekilde çalışma zamanında somut sınıflar oluşturma tercihi vardı.

Newtonsoft tarafından döndürülen nesneyi saran çalışma zamanında bir proxy sınıfı oluşturmaya karar verdim.

Bu yaklaşımın avantajı, sınıfın somut bir uygulamasını gerektirmemesi ve iç içe geçmiş arabirimlerin herhangi bir derinliğini otomatik olarak işleyebilmesidir. Blogumda bunun hakkında daha fazlasını görebilirsiniz .

using Castle.DynamicProxy;
using Newtonsoft.Json.Linq;
using System;
using System.Reflection;

namespace LL.Utilities.Std.Json
{
    public static class JObjectExtension
    {
        private static ProxyGenerator _generator = new ProxyGenerator();

        public static dynamic toProxy(this JObject targetObject, Type interfaceType) 
        {
            return _generator.CreateInterfaceProxyWithoutTarget(interfaceType, new JObjectInterceptor(targetObject));
        }

        public static InterfaceType toProxy<InterfaceType>(this JObject targetObject)
        {

            return toProxy(targetObject, typeof(InterfaceType));
        }
    }

    [Serializable]
    public class JObjectInterceptor : IInterceptor
    {
        private JObject _target;

        public JObjectInterceptor(JObject target)
        {
            _target = target;
        }
        public void Intercept(IInvocation invocation)
        {

            var methodName = invocation.Method.Name;
            if(invocation.Method.IsSpecialName && methodName.StartsWith("get_"))
            {
                var returnType = invocation.Method.ReturnType;
                methodName = methodName.Substring(4);

                if (_target == null || _target[methodName] == null)
                {
                    if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                    {

                        invocation.ReturnValue = null;
                        return;
                    }

                }

                if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                {
                    invocation.ReturnValue = _target[methodName].ToObject(returnType);
                }
                else
                {
                    invocation.ReturnValue = ((JObject)_target[methodName]).toProxy(returnType);
                }
            }
            else
            {
                throw new NotImplementedException("Only get accessors are implemented in proxy");
            }

        }
    }



}

Kullanımı:

var jObj = JObject.Parse(input);
InterfaceType proxyObject = jObj.toProxy<InterfaceType>();

Teşekkürler! Bu, gelen json üzerinde kısıtlamalara zorlamadan dinamik yazmayı (ördek yazma) doğru şekilde destekleyen tek cevaptır.
Philip Pittle

Sorun değil. Orada hiçbir şey olmadığını görünce biraz şaşırdım. O orijinal örnekten bu yana biraz ilerledi, bu yüzden kodu paylaşmaya karar verdim. github.com/sudsy/JsonDuckTyper . Ayrıca nuget'te JsonDuckTyper olarak yayınladım. Onu geliştirmek istediğinizi fark ederseniz, bana sadece bir PR gönderin, ben de bunu kabul etmekten memnuniyet duyarım.
Sudsy

Bu alanda bir çözüm ararken github.com/ekonbenefits/impromptu-interface ile de karşılaştım. Benim durumumda dotnet core 1.0'ı desteklemediği için çalışmıyor ama sizin için işe yarayabilir.
Sudsy

Impromptu Arayüzü ile denedim, ancak Json.Net, PopulateObjectImpromptu Arayüzü tarafından oluşturulan proxy üzerinde mutlu değildi . Ne yazık ki Duck Typing'e gitmekten vazgeçtim - istenen arayüzün mevcut bir uygulamasını bulmak ve bunu kullanmak için yansımayı kullanan özel bir Json Sözleşme Serileştiricisi oluşturmak daha kolaydı.
Philip Pittle

1

Bu JsonKnownTypes'i kullanın, kullanım için çok benzer bir yol, sadece json'a ayırıcı ekler:

[JsonConverter(typeof(JsonKnownTypeConverter<Interface1>))]
[JsonKnownType(typeof(MyClass), "myClass")]
public interface Interface1
{  }
public class MyClass : Interface1
{
    public string Something;
}

Eğer seri Şimdi ne zaman json nesne eklemek olacaktır "$type"ile "myClass"değer ve bu serisini için kullanmak olacak

Json:

{"Something":"something", "$type":"derived"}

0

Çözümüm yapıcıya arayüz öğeleri eklendi.

public class Customer: ICustomer{
     public Customer(Details details){
          Details = details;
     }

     [JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
     public IDetails Details {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.