Casting-Schnittstellen für die Deserialisierung in JSON.NET

128

Ich versuche, einen Reader einzurichten, der JSON-Objekte von verschiedenen Websites aufnimmt (Think Information Scraping) und sie in C # -Objekte übersetzt. Ich verwende derzeit JSON.NET für den Deserialisierungsprozess. Das Problem, auf das ich stoße, ist, dass es nicht weiß, wie man Eigenschaften auf Schnittstellenebene in einer Klasse behandelt. Also etwas von der Natur:

public IThingy Thing

Wird den Fehler erzeugen:

Es konnte keine Instanz vom Typ IThingy erstellt werden. Typ ist eine Schnittstelle oder abstrakte Klasse und kann nicht instanziiert werden.

Es ist relativ wichtig, dass es sich um ein IThingy im Gegensatz zu einem Thingy handelt, da der Code, an dem ich arbeite, als vertraulich angesehen wird und Unit-Tests sehr wichtig sind. Das Verspotten von Objekten für Atomtestskripte ist mit vollwertigen Objekten wie Thingy nicht möglich. Sie müssen eine Schnittstelle sein.

Ich habe jetzt schon eine Weile über die Dokumentation von JSON.NET nachgedacht, und die Fragen, die ich auf dieser Website dazu finden konnte, stammen alle von vor über einem Jahr. Irgendeine Hilfe?

Wenn es darauf ankommt, ist meine App auch in .NET 4.0 geschrieben.

tmesser
quelle

Antworten:

115

@SamualDavis bot eine großartige Lösung für eine verwandte Frage , die ich hier zusammenfassen werde.

Wenn Sie einen JSON-Stream in eine konkrete Klasse mit Schnittstelleneigenschaften deserialisieren müssen, können Sie die konkreten Klassen als Parameter in einen Konstruktor für die Klasse aufnehmen! Der NewtonSoft-Deserializer ist intelligent genug, um herauszufinden, dass er diese konkreten Klassen zum Deserialisieren der Eigenschaften verwenden muss.

Hier ist ein Beispiel:

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; }
}
Mark Meuer
quelle
15
Wie würde das mit einer ICollection funktionieren? ICollection <IGuest> Gäste {get; set;}
DrSammyD
12
Es funktioniert mit ICollection <ConcreteClass>, also funktioniert ICollection <Guest>. Genau wie zu Ihrer Information können Sie das Attribut [JsonConstructor] auf Ihren Konstruktor setzen, so dass es standardmäßig verwendet wird, wenn Sie zufällig mehrere Konstruktoren haben
DrSammyD
6
Ich habe das gleiche Problem, in meinem Fall habe ich mehrere Implementierungen der Schnittstelle (in Ihrem Beispiel ist die Schnittstelle ILocation). Was ist, wenn es Klassen wie MyLocation, VIPLocation, OrdinaryLocation gibt? Wie ordne ich diese der Location-Eigenschaft zu? Wenn Sie nur eine Implementierung wie MyLocation haben, ist dies einfach, aber wie geht das, wenn es mehrere Implementierungen von ILocation gibt?
ATHER
10
Wenn Sie mehr als einen Konstruktor haben, können Sie Ihren speziellen Konstruktor mit dem [JsonConstructor]Attribut markieren .
Dr. Rob Lang
26
Das ist überhaupt nicht in Ordnung. Der Sinn der Verwendung von Schnittstellen besteht darin, die Abhängigkeitsinjektion zu verwenden. Wenn Sie dies jedoch mit einem objekttypisierten Parameter tun, der von Ihrem Konstruktor benötigt wird, vermasseln Sie den Punkt, eine Schnittstelle als Eigenschaft zu haben, völlig.
Jérôme MEVEL
57

(Von dieser Frage kopiert )

In Fällen, in denen ich keine Kontrolle über den eingehenden JSON hatte (und daher nicht sicherstellen kann, dass er eine $ type-Eigenschaft enthält), habe ich einen benutzerdefinierten Konverter geschrieben, mit dem Sie nur den konkreten Typ explizit angeben können:

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

Dies verwendet nur die Standard-Serializer-Implementierung von Json.Net, während der konkrete Typ explizit angegeben wird.

Eine Übersicht finden Sie in diesem Blogbeitrag . Der Quellcode ist unten:

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);
    }
}
Steve Greatrex
quelle
11
Ich mag diesen Ansatz sehr und habe ihn auf unser eigenes Projekt angewendet. Ich habe sogar eine hinzugefügt ConcreteListTypeConverter<TInterface, TImplementation>, um Klassenmitglieder vom Typ zu behandeln IList<TInterface>.
Oliver
3
Das ist ein tolles Stück Code. Es könnte jedoch schöner sein, den tatsächlichen Code für concreteTypeConverterdie Frage zu haben.
Chris
2
@Oliver - Können Sie Ihre ConcreteListTypeConverter<TInterface, TImplementation>Implementierung veröffentlichen?
Michael
2
Und wenn Sie zwei Implementierer von ISomething haben?
Bdaniel7
56

Warum einen Konverter verwenden? Es gibt eine native Funktionalität Newtonsoft.Json, um genau dieses Problem zu lösen:

Stellen Sie TypeNameHandlingdas JsonSerializerSettingsin einTypeNameHandling.Auto

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

Dadurch wird jeder Typ in den JSON eingefügt, der nicht als konkrete Instanz eines Typs, sondern als Schnittstelle oder abstrakte Klasse gespeichert wird.

Stellen Sie sicher, dass Sie dieselben Einstellungen für die Serialisierung und Deserialisierung verwenden .

Ich habe es getestet und es funktioniert wie ein Zauber, auch mit Listen.

Suchergebnisse Web-Ergebnis mit Site-Links

⚠️ WARNUNG :

Verwenden Sie dies nur für JSON aus einer bekannten und vertrauenswürdigen Quelle. Benutzer snipsnipsnip richtig erwähnt, dass dies in der Tat eine Vunerability ist.

Weitere Informationen finden Sie unter CA2328 und SCS0028 .


Quelle und eine alternative manuelle Implementierung: Code Inside Blog

Mafii
quelle
3
Perfekt, dies half mir für einen schnellen und schmutzigen tiefen Klon ( stackoverflow.com/questions/78536/deep-cloning-objects )
Compufreak
1
@ Shimmy Objects: "Geben Sie den .NET-Typnamen an, wenn Sie in eine JSON-Objektstruktur serialisieren." Auto: Geben Sie den .NET-Typnamen an, wenn der Typ des zu serialisierenden Objekts nicht mit dem deklarierten Typ übereinstimmt. Beachten Sie, dass dies standardmäßig nicht das serialisierte Stammobjekt umfasst. Um den Typnamen des Stammobjekts in JSON aufzunehmen, müssen Sie ein Stammtypobjekt mit SerializeObject (Object, Type, JsonSerializerSettings) oder Serialize (JsonWriter, Object, Type) angeben
Mafii
4
Ich habe es gerade bei Deserialization versucht und es funktioniert nicht. Die Betreffzeile dieser Frage zum Stapelüberlauf lautet "Casting-Schnittstellen für die Deserialisierung in JSON.NET"
Justin Russo,
3
@ JustinRusso es funktioniert nur, wenn der JSON mit der gleichen Einstellung serialisiert wurde
Mafii
3
Upvote für die schnelle, wenn nicht schmutzige Lösung. Wenn Sie nur Konfigurationen serialisieren, funktioniert dies. Es ist besser, die Entwicklung zu stoppen, um Konverter zu bauen, und es ist sicherlich besser, jede injizierte Eigenschaft zu dekorieren. serializer.TypeNameHandling = TypeNameHandling.Auto; JsonConvert.DefaultSettings (). TypeNameHandling = TypeNameHandling.Auto;
Sean Anderson
39

Um die Deserialisierung mehrerer Schnittstellenimplementierungen zu ermöglichen, können Sie JsonConverter verwenden, jedoch nicht über ein Attribut:

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

DTOJsonConverter ordnet jede Schnittstelle einer konkreten Implementierung zu:

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 wird nur für den Deserializer benötigt. Der Serialisierungsprozess bleibt unverändert. Das Json-Objekt muss keine konkreten Typnamen einbetten.

Dieser SO-Beitrag bietet die gleiche Lösung einen Schritt weiter mit einem generischen JsonConverter.

Eric Boumendil
quelle
Würde der Aufruf der WriteJson-Methode an serializer.Serialize nicht einen Stapelüberlauf verursachen, da der Aufruf von serialize für den vom Konverter serialisierten Wert dazu führen würde, dass die WriteJson-Methode des Konverters erneut rekursiv aufgerufen wird?
Triynko
Dies sollte nicht der Fall sein, wenn die CanConvert () -Methode ein konsistentes Ergebnis zurückgibt.
Eric Boumendil
3
Warum vergleichen Sie FullNames, wenn Sie nur Typen direkt vergleichen können?
Alex Zhukovskiy
Nur Typen zu vergleichen ist auch in Ordnung.
Eric Boumendil
23

Verwenden Sie diese Klasse, um den abstrakten Typ dem realen Typ zuzuordnen:

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

... und wenn deserialisieren:

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

        JsonConvert.DeserializeObject(json, type, settings);
Gildor
quelle
1
Ich mag eine nette, prägnante Antwort, die mein Problem löst. Keine Notwendigkeit für Autofac oder irgendetwas!
Ben Power
3
Es lohnt sich, dies in die Konverterklassendeklaration aufzunehmen: where TReal : TAbstractum sicherzustellen, dass es in den Typ umgewandelt werden kann
Artemious
1
Ein vollständigeres wo könnte sein where TReal : class, TAbstract, new().
Erik Philips
2
Ich habe diesen Konverter auch mit struct verwendet. Ich glaube, "where TReal: TAbstract" ist genug. Vielen Dank an alle.
Gildor
2
Gold! Schicker Weg zu gehen.
SwissCoder
12

Nicholas Westby bot eine großartige Lösung in einem großartigen Artikel .

Wenn Sie JSON in eine von vielen möglichen Klassen deserialisieren möchten, die eine solche Schnittstelle implementieren:

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

Sie können einen benutzerdefinierten JSON-Konverter verwenden:

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

Außerdem müssen Sie die Eigenschaft "Profession" mit einem JsonConverter-Attribut dekorieren, damit Sie wissen, dass Sie Ihren benutzerdefinierten Konverter verwenden können:

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

Und dann können Sie Ihre Klasse mit einem Interface besetzen:

Person person = JsonConvert.DeserializeObject<Person>(jsonString);
A. Morel
quelle
8

Zwei Dinge, die Sie versuchen könnten:

Implementieren Sie ein Try / Parse-Modell:

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

Wenn Sie dies in Ihrem Objektmodell tun können, implementieren Sie eine konkrete Basisklasse zwischen IPerson und Ihren Blattobjekten und deserialisieren Sie diese.

Der erste kann möglicherweise zur Laufzeit fehlschlagen, der zweite erfordert Änderungen an Ihrem Objektmodell und homogenisiert die Ausgabe auf den kleinsten gemeinsamen Nenner.

mcw
quelle
Ein Try / Parse-Modell ist aufgrund der Skalierung, mit der ich arbeiten muss, nicht möglich. Ich muss einen Bereich von Hunderten von Basisobjekten mit noch mehr Hunderten von Stub / Helper-Objekten berücksichtigen, um eingebettete JSON-Objekte darzustellen, die häufig vorkommen. Es kommt nicht in Frage, das Objektmodell zu ändern, aber würde die Verwendung einer konkreten Basisklasse in den Eigenschaften nicht dazu führen, dass wir keine Elemente für Komponententests verspotten können? Oder bekomme ich das irgendwie rückwärts?
tmesser
Sie können weiterhin ein Modell von IPerson implementieren. Beachten Sie, dass der Typ der Organisation.Owner-Eigenschaft weiterhin IPerson ist. Für die Deserialisierung eines beliebigen Ziels müssen Sie jedoch einen konkreten Typ zurückgeben. Wenn Sie die Typdefinition nicht besitzen und nicht die Mindestmenge an Eigenschaften definieren können, die Ihr Code benötigt, ist Ihr letzter Ausweg so etwas wie eine Schlüssel- / Wertetasche. Verwenden Sie Ihren Facebook-Beispielkommentar - können Sie in einer Antwort posten, wie Ihre (eine oder mehrere) Implementierungen von ILocation aussehen? Das kann helfen, die Dinge voranzubringen.
McW
Da die primäre Hoffnung das Verspotten ist, ist die ILocation-Schnittstelle eigentlich nur eine Fassade für das konkrete Location-Objekt. Ein kurzes Beispiel, das ich gerade ausgearbeitet habe, wäre so etwas ( pastebin.com/mWQtqGnB ) für die Schnittstelle und dies ( pastebin.com/TdJ6cqWV ) für das konkrete Objekt.
Messer
Und den nächsten Schritt zu gehen, ist dies ein Beispiel dafür , was IPage aussehen würde ( pastebin.com/iuGifQXp ) und Seite ( pastebin.com/ebqLxzvm ). Das Problem ist natürlich, dass die Deserialisierung von Page im Allgemeinen gut funktioniert, aber erstickt, wenn die ILocation-Eigenschaft erreicht wird.
tmesser
Ok, denken Sie also an die Objekte, die Sie tatsächlich kratzen und deserialisieren. Ist es im Allgemeinen so, dass die JSON-Daten mit einer einzelnen konkreten Klassendefinition übereinstimmen? Bedeutet das (hypothetisch), dass Sie nicht auf "Standorte" mit zusätzlichen Eigenschaften stoßen würden, die den Standort als konkreten Typ für das deserialisierte Objekt ungeeignet machen würden? In diesem Fall sollte das Zuweisen der ILocation-Eigenschaft von Page zu einem "LocationConverter" funktionieren. Wenn nicht, und weil die JSON-Daten nicht immer einer starren oder konsistenten Struktur entsprechen (wie ILocation), dann (... Fortsetzung)
mcw
8

Ich fand das nützlich. Du könntest auch.

Beispiel Verwendung

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

Benutzerdefinierter Erstellungskonverter

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

Json.NET-Dokumentation

Smiggleworth
quelle
1
Keine praktikable Lösung. Adressiert keine Listen und führt dazu, dass Dekorateure / Anmerkungen überall verteilt werden.
Sean Anderson
5

Für diejenigen, die neugierig auf den ConcreteListTypeConverter sind, auf den Oliver verwiesen hat, ist hier mein Versuch:

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);
    }
}
Matt M.
quelle
1
Ich bin verwirrt mit dem überschriebenen CanConvert(Type objectType) { return true;}. Es scheint hacky, wie genau ist das hilfreich? Ich kann mich irren, aber ist das nicht so, als würde man einem kleineren unerfahrenen Kämpfer sagen, dass er den Kampf gewinnen wird, egal welcher Gegner?
Chef_Code
4

Für das, was es wert ist, musste ich mich größtenteils selbst darum kümmern. Jedes Objekt verfügt über eine Deserialize- Methode (Zeichenfolge jsonStream) . Ein paar Ausschnitte davon:

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

In diesem Fall ist new Thingy (Zeichenfolge) ein Konstruktor, der die Deserialize- Methode (Zeichenfolge jsonStream) des entsprechenden konkreten Typs aufruft . Dieses Schema wird weiter nach unten und unten verschoben, bis Sie zu den Basispunkten gelangen, die json.NET gerade verarbeiten kann.

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

Und so weiter und so fort. Mit diesem Setup konnte ich json.NET-Setups bereitstellen, die verarbeitet werden können, ohne dass ein großer Teil der Bibliothek selbst umgestaltet werden muss oder unhandliche Try / Parse-Modelle verwendet werden müssen, die aufgrund der Anzahl der beteiligten Objekte unsere gesamte Bibliothek blockiert hätten. Dies bedeutet auch, dass ich alle JSON-Änderungen an einem bestimmten Objekt effektiv verarbeiten kann und mich nicht um alles kümmern muss, was das Objekt berührt. Es ist keineswegs die ideale Lösung, aber es funktioniert recht gut mit unseren Unit- und Integrationstests.

tmesser
quelle
4

Angenommen, eine Autofac-Einstellung wie die folgende:

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

Nehmen wir dann an, Ihre Klasse ist wie folgt:

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

Daher könnte die Verwendung des Resolvers bei der Deserialisierung wie folgt aussehen:

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

Weitere Informationen finden Sie unter http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm

Oh mein Gott
quelle
Ich werde dies als die beste Lösung abstimmen. DI wurde heutzutage von C # Web-Entwicklern so häufig verwendet, dass es sich gut als zentraler Ort für die Typkonvertierung durch den Resolver eignet.
Appletwo
3

Kein Objekt wird immer sein , eine iThingy als Schnittstellen alle abstrakten per Definition.

Das Objekt, das Sie zuerst serialisiert haben, war von einem konkreten Typ und implementierte die abstrakte Schnittstelle. Sie müssen den gleichen Beton haben Klasse muss die serialisierten Daten wiederbeleben.

Das resultierende Objekt ist dann von einem Typ, der die von Ihnen gesuchte abstrakte Schnittstelle implementiert .

Aus der Dokumentation folgt, dass Sie verwenden können

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

beim Deserialisieren, um JSON.NET über den konkreten Typ zu informieren.

Sean Kinsey
quelle
Das ist genau der Beitrag von vor über einem Jahr, auf den ich mich bezog. Der einzige wichtige Vorschlag (das Schreiben von benutzerdefinierten Konvertern) ist mit der Skala, die ich berücksichtigen muss, nicht schrecklich machbar. JSON.NET hat sich im vergangenen Jahr stark verändert. Ich verstehe die Unterscheidung zwischen einer Klasse und einer Schnittstelle perfekt, aber C # unterstützt auch implizite Konvertierungen von einer Schnittstelle zu einem Objekt, das die Schnittstelle hinsichtlich der Eingabe implementiert. Ich frage im Wesentlichen, ob es eine Möglichkeit gibt, JSON.NET mitzuteilen, welches Objekt diese Schnittstelle implementieren wird.
tmesser
Es war alles da in der Antwort, auf die ich Sie hingewiesen habe. Stellen Sie sicher, dass eine _typeEigenschaft vorhanden ist, die den zu verwendenden Betontyp signalisiert.
Sean Kinsey
Und ich bezweifle stark, dass C # jede Art von "impliziter" Typumwandlung von einer Variablen unterstützt, die als Schnittstelle zu einem konkreten Typ deklariert ist, ohne irgendwelche Hinweise.
Sean Kinsey
Sofern ich es nicht falsch gelesen habe, sollte sich die Eigenschaft _type in dem zu serialisierenden JSON befinden. Das funktioniert gut, wenn Sie nur das deserialisieren, was Sie bereits serialisiert haben, aber das ist hier nicht der Fall. Ich ziehe JSON von einer Reihe von Websites ab, die diesem Standard nicht folgen werden.
tmesser
@YYY - Steuern Sie sowohl die Serialisierung als auch die Deserialisierung von der Quell-JSON? Denn letztendlich müssen Sie entweder den konkreten Typ in den serialisierten JSON einbetten, um ihn beim Deserialisieren zu verwenden, oder Sie müssen eine Art Try / Parse-Modell verwenden, das den konkreten Typ zur Laufzeit erkennt / versucht, ihn zu erkennen und rufen Sie den entsprechenden Deserializer auf.
McW
3

Meine Lösung für diese, die ich mag, weil sie sehr allgemein ist, lautet wie folgt:

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

}}

Sie können es offensichtlich und trivial in einen noch allgemeineren Konverter konvertieren, indem Sie einen Konstruktor hinzufügen, der ein Argument vom Typ Dictionary <Typ, Typ> verwendet, mit dem die Instanzvariable der Konvertierung instanziiert wird.

Simon Brooke
quelle
3

Einige Jahre später hatte ich ein ähnliches Problem. In meinem Fall gab es stark verschachtelte Schnittstellen und eine Präferenz für das Generieren der konkreten Klassen zur Laufzeit, damit es mit einer generischen Klasse funktioniert.

Ich habe beschlossen, zur Laufzeit eine Proxy-Klasse zu erstellen, die das von Newtonsoft zurückgegebene Objekt umschließt.

Der Vorteil dieses Ansatzes besteht darin, dass keine konkrete Implementierung der Klasse erforderlich ist und jede Tiefe verschachtelter Schnittstellen automatisch verarbeitet werden kann. Sie können mehr darüber in meinem Blog sehen .

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

        }
    }



}

Verwendung:

var jObj = JObject.Parse(input);
InterfaceType proxyObject = jObj.toProxy<InterfaceType>();
Schaumig
quelle
Vielen Dank! Dies ist die einzige Antwort, die die dynamische Typisierung (Ententypisierung) ordnungsgemäß unterstützt, ohne Einschränkungen für den eingehenden JSON zu erzwingen.
Philip Pittle
Kein Problem. Ich war ein bisschen überrascht zu sehen, dass da draußen nichts war. Es hat sich seit diesem ursprünglichen Beispiel etwas weiterentwickelt, also habe ich beschlossen, den Code zu teilen. github.com/sudsy/JsonDuckTyper . Ich habe es auch auf Nuget als JsonDuckTyper veröffentlicht. Wenn Sie feststellen, dass Sie es verbessern möchten, senden Sie mir einfach eine PR, und ich werde mich gerne darum kümmern.
Sudsy
Als ich nach einer Lösung in diesem Bereich suchte, stieß ich auch auf github.com/ekonbenefits/impromptu-interface . In meinem Fall funktioniert es nicht, da es Dotnet Core 1.0 nicht unterstützt, aber es könnte für Sie funktionieren.
Sudsy
Ich habe es mit Impromptu Interface versucht, aber Json.Net war nicht glücklich, einen PopulateObjectauf dem von Impromptu Interface generierten Proxy zu machen. Ich habe es leider aufgegeben, mich für Duck Typing zu entscheiden - es war einfach einfacher, einen benutzerdefinierten Json Contract Serializer zu erstellen, der mithilfe von Reflection eine vorhandene Implementierung der angeforderten Schnittstelle fand und diese verwendete.
Philip Pittle
1

Verwenden Sie diese JsonKnownTypes , es ist sehr ähnlich zu verwenden, es fügt nur einen Diskriminator zu json hinzu:

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

Wenn Sie nun ein Objekt in json serialisieren, wird es "$type"mit dem "myClass"Wert addiert und zum Deserialisieren verwendet

Json:

{"Something":"something", "$type":"derived"}
Dmitry
quelle
0

Meine Lösung wurde die Schnittstellenelemente im Konstruktor hinzugefügt.

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

     [JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
     public IDetails Details {get; set;}
}
Jorge Santos Neill
quelle