Utilizzo dei convertitori Json.NET per deserializzare le proprietà


88

Ho una definizione di classe che contiene una proprietà che restituisce un'interfaccia.

public class Foo
{ 
    public int Number { get; set; }

    public ISomething Thing { get; set; }
}

Il tentativo di serializzare la classe Foo utilizzando Json.NET mi dà un messaggio di errore come "Impossibile creare un'istanza di tipo 'ISomething'. ISomething può essere un'interfaccia o una classe astratta."

Esiste un attributo o un convertitore Json.NET che mi consenta di specificare una Somethingclasse concreta da utilizzare durante la deserializzazione?


Credo che sia necessario specificare un nome di proprietà che ottiene / imposta ISomething
ram

Io ho. Sto usando l'abbreviazione per le proprietà implementate automaticamente introdotte in C # 3.5. msdn.microsoft.com/en-us/library/bb384054.aspx
dthrasher

4
Non è qualcosa del tipo. Penso che Ram abbia ragione, hai ancora bisogno di un nome di proprietà. So che questo non è correlato al tuo problema, ma il tuo commento sopra mi ha fatto pensare che mi mancasse qualche nuova funzionalità in .NET che ti consentisse di specificare una proprietà senza un nome.
Mr Moose

Risposte:


92

Una delle cose che puoi fare con Json.NET è:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;

JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

Il TypeNameHandlingflag aggiungerà una $typeproprietà a JSON, che consente a Json.NET di sapere in quale tipo concreto ha bisogno per deserializzare l'oggetto. Ciò consente di deserializzare un oggetto pur soddisfacendo un'interfaccia o una classe base astratta.

Lo svantaggio, tuttavia, è che questo è molto specifico di Json.NET. Il $typesarà un tipo completamente qualificato, quindi se si sta serializzazione con informazioni di tipo ,, le esigenze deserializzatore per essere in grado di capirlo pure.

Documentazione: impostazioni di serializzazione con Json.NET


Interessante. Dovrò giocare con questo. Bel suggerimento!
dthrasher

2
Per Newtonsoft.Json funziona in modo simile, ma la proprietà è "$ type"
Jaap

È stato troppo facile!
Shimmy Weitzhandler

1
Fai attenzione a possibili problemi di sicurezza qui durante l'utilizzo TypeNameHandling. Per i dettagli, vedere le avvertenze di TypeNameHandling in Newtonsoft Json .
dbc

Ieri ho lottato come un matto con i convertitori, e questo era molto meglio e meglio comprensibile, grazie !!!
Horothenic

52

Puoi ottenere ciò tramite l'uso della classe JsonConverter. Supponiamo di avere una classe con una proprietà di interfaccia;

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

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

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

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

Il tuo JsonConverter è responsabile della serializzazione e de-serializzazione della proprietà sottostante;

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

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    return serializer.Deserialize<Tycoon>(reader);
  }

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

Quando lavori con un'organizzazione deserializzata tramite Json.Net, l'IPerson sottostante per la proprietà Owner sarà di tipo Tycoon.


Molto bella. Dovrò provare il convertitore.
dthrasher

4
Il tag "[JsonConverter (typeof (TycoonConverter))]" funzionerebbe ancora se fosse su un elenco dell'interfaccia?
Zwik

40

Invece di passare un oggetto JsonSerializerSettings personalizzato a JsonConvert.SerializeObject () con l'opzione TypeNameHandling.Objects, come accennato in precedenza, puoi semplicemente contrassegnare quella specifica proprietà dell'interfaccia con un attributo in modo che il JSON generato non venga gonfio di proprietà "$ type" su OGNI oggetto:

public class Foo
{
    public int Number { get; set; }

    // Add "$type" property containing type info of concrete class.
    [JsonProperty( TypeNameHandling = TypeNameHandling.Objects )]
    public ISomething { get; set; }
}

Brillante. Grazie :)
Darren Young

5
Per le raccolte di interfacce o classi astratte la proprietà è "ItemTypeNameHandling". ad esempio: [JsonProperty (ItemTypeNameHandling = TypeNameHandling.Auto)]
Anthony F

Grazie per questo!
Brudert

23

Nella versione più recente del convertitore Json Newtonsoft di terze parti è possibile impostare un costruttore con un tipo concreto relativo alla proprietà interfacciata.

public class Foo
{ 
    public int Number { get; private set; }

    public ISomething IsSomething { get; private set; }

    public Foo(int number, Something concreteType)
    {
        Number = number;
        IsSomething = concreteType;
    }
}

Finché Something implementa ISomething, dovrebbe funzionare. Inoltre, non mettere un costruttore vuoto predefinito nel caso in cui il convertitore JSon tenti di usarlo, devi forzarlo a usare il costruttore contenente il tipo concreto.

PS. questo ti permette anche di rendere privati ​​i tuoi setter.


6
Questo dovrebbe essere gridato dai tetti! È vero, aggiunge vincoli all'implementazione concreta, ma è molto più semplice degli altri approcci per quelle situazioni in cui può essere utilizzato.
Mark Meuer

3
E se avessimo più di un costruttore con più tipi di calcestruzzo, lo saprà ancora?
Teoman shipahi

1
Questa risposta è così elegante rispetto a tutte le assurdità contorte che dovresti fare altrimenti. Questa dovrebbe essere la risposta accettata. Un avvertimento nel mio caso, tuttavia, è stato che ho dovuto aggiungere [JsonConstructor] prima del costruttore affinché funzionasse ... Sospetto che l'utilizzo di questo solo su UNO dei tuoi costruttori di cemento avrebbe risolto il tuo problema (vecchio di 4 anni) @Teomanshipahi
nacitar sevaht

@nacitarsevaht posso tornare indietro e risolvere il mio problema ora :) comunque non ricordo nemmeno cosa fosse, ma quando rivedo questa è una buona soluzione per alcuni casi.
Teoman shipahi

usiamo anche questo, ma preferisco il convert nella maggior parte dei casi perché accoppiare il tipo concrete al costruttore vanifica lo scopo di utilizzare un'interfaccia per la proprietà in primo luogo!
gabe

19

Ho avuto lo stesso problema, quindi ho creato il mio convertitore che utilizza argomenti di tipi noti.

public class JsonKnownTypeConverter : JsonConverter
{
    public IEnumerable<Type> KnownTypes { get; set; }

    public JsonKnownTypeConverter(IEnumerable<Type> knownTypes)
    {
        KnownTypes = knownTypes;
    }

    protected object Create(Type objectType, JObject jObject)
    {
        if (jObject["$type"] != null)
        {
            string typeName = jObject["$type"].ToString();
            return Activator.CreateInstance(KnownTypes.First(x =>typeName.Contains("."+x.Name+",")));
        }

        throw new InvalidOperationException("No supported type");
    }

    public override bool CanConvert(Type objectType)
    {
        if (KnownTypes == null)
            return false;

        return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);
        // Create target object based on JObject
        var target = Create(objectType, jObject);
        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);
        return target;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Ho definito due metodi di estensione per deserializzare e serializzare:

public static class AltiJsonSerializer
{
    public static T DeserializeJson<T>(this string jsonString, IEnumerable<Type> knownTypes = null)
    {
        if (string.IsNullOrEmpty(jsonString))
            return default(T);

        return JsonConvert.DeserializeObject<T>(jsonString,
                new JsonSerializerSettings
                {
                    TypeNameHandling = TypeNameHandling.Auto, 
                    Converters = new List<JsonConverter>
                        (
                            new JsonConverter[]
                            {
                                new JsonKnownTypeConverter(knownTypes)
                            }
                        )
                }
            );
    }

    public static string SerializeJson(this object objectToSerialize)
    {
        return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented,
        new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
    }
}

Puoi definire il tuo modo di confrontare e identificare i tipi nei convertiti, io uso solo il nome della classe.


1
Questo JsonConverter è fantastico, l'ho usato ma ho affrontato un paio di problemi che ho risolto in questo modo: - Utilizzando JsonSerializer.CreateDefault () invece di Populate, perché il mio oggetto aveva una gerarchia più profonda. - Utilizzo della riflessione per recuperare il costruttore e istanziarlo nel metodo Create ()
Aurel

3

Normalmente ho sempre usato la soluzione con TypeNameHandlingcome suggerito da DanielT, ma nei casi qui non ho avuto il controllo sul JSON in arrivo (e quindi non posso assicurarmi che includa una $typeproprietà) ho scritto un convertitore personalizzato che ti permette solo di specificare esplicitamente il tipo di cemento:

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

Questo utilizza solo l'implementazione del serializzatore predefinita da Json.Net specificando esplicitamente il tipo concreto.

Il codice sorgente e una panoramica sono disponibili in questo post del blog .


1
Questa è un'ottima soluzione. Saluti.
JohnMetta

2

Volevo solo completare l'esempio che @Daniel T. ci ha mostrato sopra:

Se stai usando questo codice per serializzare il tuo oggetto:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

Il codice per deserializzare il json dovrebbe essere simile a questo:

var settings = new JsonSerializerSettings(); 
settings.TypeNameHandling = TypeNameHandling.Objects;
var entity = JsonConvert.DeserializeObject<EntityType>(json, settings);

Questo è il modo in cui un json viene conformato quando si utilizza la TypeNameHandlingbandiera:inserisci qui la descrizione dell'immagine


-5

Mi sono chiesto la stessa cosa, ma temo che non possa essere fatto.

Vediamola in questo modo. Consegni a JSon.net una stringa di dati e un tipo in cui deserializzare. Cosa deve fare JSON.net quando arriva a quel qualcosa IS? Non può creare un nuovo tipo di ISomething perché ISomething non è un oggetto. Inoltre non può creare un oggetto che implementi ISomething, poiché non ha la più pallida idea di quale dei tanti oggetti che possono ereditare ISomething dovrebbe usare. Le interfacce sono qualcosa che può essere serializzato automaticamente, ma non deserializzato automaticamente.

Quello che farei sarebbe cercare di sostituire ISomething con una classe base. Usando quello potresti essere in grado di ottenere l'effetto che stai cercando.


1
Mi rendo conto che non funzionerà "fuori dagli schemi". Ma mi chiedevo se ci fosse qualche attributo come "[JsonProperty (typeof (SomethingBase))]" che potrei usare per fornire una classe concreta.
dthrasher

Allora perché non usare SomethingBase invece di ISomething nel codice sopra? Si potrebbe sostenere che stiamo anche guardando a questo nel modo sbagliato, poiché le interfacce non dovrebbero essere utilizzate nella serializzazione, poiché definiscono semplicemente "l'interfaccia" di comunicazione con una data classe. Serializzare un'interfaccia tecnicamente non ha senso, così come serializzare una classe astratta. Quindi, mentre "potrebbe essere fatto", direi che "non dovrebbe essere fatto".
Timothy Baldridge,

Hai esaminato una delle classi nello spazio dei nomi Newtonsoft.Json.Serialization? in particolare la classe JsonObjectContract?
johnny,

-9

Ecco un riferimento a un articolo scritto da ScottGu

Sulla base di ciò, ho scritto del codice che penso potrebbe essere utile

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

}

public class School : IEducationalInstitute
{
    private string name;
    #region IEducationalInstitute Members

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    #endregion
}

public class Student 
{
    public IEducationalInstitute LocalSchool { get; set; }

    public int ID { get; set; }
}

public static class JSONHelper
{
    public static string ToJSON(this object obj)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        return serializer.Serialize(obj);
    }
    public  static string ToJSON(this object obj, int depth)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        serializer.RecursionLimit = depth;
        return serializer.Serialize(obj);
    }
}

Ed è così che lo chiameresti

School myFavSchool = new School() { Name = "JFK High School" };
Student sam = new Student()
{
    ID = 1,
    LocalSchool = myFavSchool
};
string jSONstring = sam.ToJSON();

Console.WriteLine(jSONstring);
//Result {"LocalSchool":{"Name":"JFK High School"},"ID":1}

Se ho capito bene, non credo sia necessario specificare una classe concreta che implementa l'interfaccia per la serializzazione JSON.


1
L'esempio usa JavaScriptSerializer, una classe in .NET Framework. Sto usando Json.NET come serializzatore. codeplex.com/Json
dthrasher

3
Non si riferisce alla domanda originale, Json.NET è stato esplicitamente menzionato lì.
Oliver
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.