web-dev-qa-db-ger.com

Casting-Schnittstellen zur Deserialisierung in JSON.NET

Ich versuche, einen Reader einzurichten, der JSON-Objekte von verschiedenen Websites übernimmt (think information scraping) und diese in C # -Objekte übersetzt. Ich verwende derzeit JSON.NET für den Deserialisierungsprozess. Das Problem, dem ich begegne, ist, dass es nicht weiß, wie die Eigenschaften der Schnittstellenebene in einer Klasse behandelt werden. Also etwas von der Natur:

public IThingy Thing

Wird den Fehler erzeugen:

Instanz des Typs IThingy konnte nicht erstellt werden. Type 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 sensibel gilt und Unit-Tests sehr wichtig sind. Das Verspotten von Objekten für atomare Testskripte ist mit vollwertigen Objekten wie Thingy nicht möglich. Sie müssen eine Schnittstelle sein.

Ich habe mich schon länger mit der Dokumentation von JSON.NET beschäftigt, und die Fragen, die ich auf dieser Site finden könnte, stammen alle aus über einem Jahr. Irgendeine Hilfe?

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

105
tmesser

@SamualDavis bot eine großartige Lösung in einer bezogenen Frage , die ich hier zusammenfassen möchte.

Wenn Sie einen JSON-Stream in eine konkrete Klasse deserialisieren müssen, die über Schnittstelleneigenschaften verfügt, können Sie die konkreten Klassen als Parameter in einen Konstruktor für die Klasse einschließen!diese konkreten Klassen verwenden, um die Eigenschaften zu deserialisieren. 

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; }
}
96
Mark Meuer

(Kopiert aus diese Frage )

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, der es Ihnen nur erlaubt, den konkreten Typ explizit anzugeben:

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

Hierbei wird lediglich die Standard-Implementierung des Serializers von Json.Net verwendet, wobei der konkrete Typ explizit angegeben wird.

Eine Übersicht gibt es in diesem Blogbeitrag . 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);
    }
}
46
Steve Greatrex

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 bildet jede Schnittstelle mit einer konkreten Implementierung ab:

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 ist unverändert. Das Json-Objekt muss keine konkreten Typnamen einbetten.

Dieses SO post bietet die gleiche Lösung mit einem generischen JsonConverter einen Schritt weiter.

36
Eric Boumendil

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

Setze TypeNameHandling in der JsonSerializerSettings auf TypeNameHandling.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 gehalten wird.

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

Quelle und eine alternative manuelle Implementierung: Code Inside Blog

36
Mafii

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

Verwendungsbeispiel

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

Custom Creation Converter

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

Json.NET Dokumentation

8
smiggleworth

Verwenden Sie diese Klasse, um einen abstrakten Typ einem echten 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);
5
Gildor

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 sie. 

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

5
mcw0933

Angenommen, eine AutoFac-Einstellung ist wie folgt:

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 an, Ihre Klasse sieht so aus: 

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 folgendermaßen 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 in http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm

4
OmG

Für diejenigen, die neugierig auf den ConcreteListTypeConverter sind, auf den von Oliver verwiesen wurde, 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);
    }
}
4
Matt M

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

Wenn Sie die Deserialisierung von JSON in eine von vielen möglichen Klassen aufnehmen 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;
    }
}

Und Sie müssen die Eigenschaft "Profession" mit einem JsonConverter-Attribut versehen, um den benutzerdefinierten Konverter verwenden zu 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);
2
A. Morel

Für das, was es wert ist, musste ich meistens selbst damit umgehen. Jedes Objekt verfügt über eine Deserialize (String jsonStream) Methode. Einige 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 (string) ein Konstruktor, der die Deserialize (string jsonStream) -Methode des entsprechenden konkreten Typs aufruft. Dieses Schema geht weiter abwärts und abwärts, bis Sie zu den Basispunkten gelangen, die json.NET nur verarbeiten kann.

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

Und so weiter und so fort. Dieses Setup ermöglichte es mir, json.NET-Setups zur Verfügung zu stellen, die es handhaben kann, ohne einen großen Teil der Bibliothek selbst umgestalten zu müssen oder unhandliche Try/Parse-Modelle zu verwenden, die aufgrund der Anzahl der beteiligten Objekte unsere gesamte Bibliothek beeinträchtigt hätten. Es bedeutet auch, dass ich alle Json-Änderungen an einem bestimmten Objekt effektiv bearbeiten kann, und ich muss mich nicht um alles kümmern, das das Objekt berührt. Es ist keineswegs die ideale Lösung, aber es funktioniert gut mit unseren Unit- und Integrationstests.

2
tmesser

Einige Jahre später hatte ich ein ähnliches Problem. In meinem Fall gab es stark verschachtelte Schnittstellen und die Präferenz, die konkreten Klassen zur Laufzeit zu generieren, damit sie mit einer generischen Klasse arbeiten würde.

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

Der Vorteil dieses Ansatzes ist, 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");
            }

        }
    }



}

Verwendungszweck:

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

Kein Objekt wird ever _ ​​be ein IThingy sein, da Schnittstellen per Definition abstrakt sind.

Das Objekt, das Sie zum ersten Mal serialisiert haben, war vom Typ Beton und implementierte die Schnittstelle Zusammenfassung. Diese konkrete-Klasse muss die serialisierten Daten wiederbeleben.

Das resultierende Objekt wird dann von einem Typ sein, der implementiert die Zusammenfassung-Schnittstelle, nach der Sie suchen.

Aus der Dokumentation folgt, dass Sie verwenden können

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

bei der Deserialisierung, um JSON.NET über den konkreten Typ zu informieren.

1
Sean Kinsey

Meine Lösung zu dieser, die ich mag, weil sie allgemein gehalten 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 <Type, Type> benötigt, mit dem die Konvertierungsinstanzvariable instanziiert wird.

1
Simon Brooke

Bei meiner Lösung wurden 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;}
}
0