JSON.NETでの逆シリアル化のためのキャストインターフェイス


128

さまざまなWebサイトからJSONオブジェクトを取り込み(情報のスクレイピングを考える)、それらをC#オブジェクトに変換するリーダーをセットアップしようとしています。私は現在、逆シリアル化プロセスにJSON.NETを使用しています。私が直面している問題は、クラス内のインターフェイスレベルのプロパティを処理する方法がわからないことです。だから自然の何か:

public IThingy Thing

エラーが発生します:

IThingyタイプのインスタンスを作成できませんでした。タイプはインターフェースまたは抽象クラスであり、インスタンス化することはできません。

私が取り組んでいるコードは機密性が高く、ユニットテストが非常に重要であるため、ThingyではなくIThingyであることが比較的重要です。Thingyのような本格的なオブジェクトでは、アトミックテストスクリプトのオブジェクトをモックすることはできません。それらはインターフェースでなければなりません。

私はしばらくJSON.NETのドキュメントを調べてきましたが、このサイトでこれに関連して見つけた質問はすべて1年以上前のものです。何か助け?

また、問題があれば、私のアプリは.NET 4.0で書かれています。


回答:


115

@SamualDavisは、関連する質問で優れた解決策を提供しました。

JSONストリームをインターフェイスプロパティを持つ具象クラスに逆シリアル化する必要がある場合は、具象クラスをパラメーターとしてクラスのコンストラクターに含めることができます。 NewtonSoftデシリアライザーは、これらの具象クラスを使用してプロパティをデシリアライズする必要があることを理解するのに十分スマートです。

次に例を示します。

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
これはICollectionでどのように機能しますか?ICollection <IGuest> Guest {get; set;}
DrSammyD 2014年

12
ICollection <ConcreteClass>と連携するため、ICollection <Guest>と連携します。参考までに、属性[JsonConstructor]をコンストラクターに配置して、複数のコンストラクターがある場合にデフォルトでそれを使用することができます
DrSammyD

6
私は同じ問題に悩まされていますが、私の場合、インターフェイスの実装がいくつかあります(例では、インターフェイスはILocationです)。MyLocation、VIPLocation、OrdinaryLocationなどのクラスがある場合はどうでしょうか。これらをLocationプロパティにマップする方法は?MyLocationのような実装が1つしかない場合は簡単ですが、ILocationの実装が複数ある場合はどうすればよいですか?
ATHER、2015年

10
複数のコンストラクターがある場合は、特別なコンストラクターを[JsonConstructor]属性でマークアップできます。
ロブラング博士、

26
これはまったく問題ありません。インターフェースを使用するポイントは、依存性注入を使用することですが、コンストラクターに必要なオブジェクト型パラメーターを使用してこれを行うと、インターフェースをプロパティとして持つポイントを完全に台無しにします。
ジェローム・MEVEL

57

この質問からコピー)

着信JSONを制御できなかった(そして$ typeプロパティが含まれていることを確認できない)場合は、具体的な型を明示的に指定できるカスタムコンバーターを作成しました。

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

これは、具体的なタイプを明示的に指定しながら、Json.Netのデフォルトのシリアライザ実装を使用するだけです。

概要はこのブログ投稿でご覧いただけます。ソースコードは以下の通りです:

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
私はこのアプローチが本当に好きで、私たち自身のプロジェクトにそれを適用しました。ConcreteListTypeConverter<TInterface, TImplementation>タイプのクラスメンバーを処理するためにを追加しましたIList<TInterface>
オリバー

3
これはすばらしいコードです。concreteTypeConverterただし、実際のコードを質問に含めた方がよい場合があります。
クリス

2
@Oliver- ConcreteListTypeConverter<TInterface, TImplementation>実装を投稿できますか?
マイケル

2
また、ISomethingの実装者が2人いる場合はどうでしょうか。
bdaniel7

56

コンバーターを使用する理由 には、Newtonsoft.Jsonこの正確な問題を解決するためのネイティブ機能があります。

セットTypeNameHandlingJsonSerializerSettingsTypeNameHandling.Auto

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

これにより、すべての型がjsonに入れられます。これは、型の具象インスタンスとしてではなく、インターフェースまたは抽象クラスとして保持されます。

シリアライゼーションとデシリアライゼーションに同じ設定を使用していることを確認してください。

私はそれをテストしました、そしてそれはリストでさえも魅力のように働きます。

サイトリンクを含む検索結果Web結果

⚠️ 警告

既知の信頼できるソースからのjsonにのみ使用してください。ユーザーsnipsnipsnipは、これが実際に脆弱性であることを正しく述べました。

詳細については、CA2328およびSCS0028を参照してください。


ソースと代替の手動実装:Code Inside Blog


3
完璧です。これは、すばやくダーティなディープクローン(stackoverflow.com/questions/78536/deep-cloning-objects)に
役立ちました

1
@Shimmyオブジェクト:「JSONオブジェクト構造にシリアル化するときに.NETタイプ名を含めます。」自動:シリアル化されるオブジェクトのタイプが宣言されたタイプと同じでない場合は、.NETタイプ名を含めます。これには、デフォルトでルート直列化オブジェクトが含まれていないことに注意してください。。JSONでルートオブジェクトのタイプ名を含めるには、あなたはSerializeObject(オブジェクト、タイプ、JsonSerializerSettings)またはシリアル化(JsonWriter、オブジェクト、タイプ)」ソースとのルート型のオブジェクトを指定する必要があります。newtonsoft.com/json/help/html/...を
マフィイ

4
これを逆シリアル化で試したところ、機能しません。このスタックオーバーフローの質問の件名は、「JSON.NETでの逆シリアル化のためのインターフェイスのキャスト」
Justin Russo

3
@JustinRussoこれは、jsonが同じ設定でシリアル化されている場合にのみ機能します
Mafii

3
汚れていなくても、迅速なソリューションに賛成票を投じます。構成をシリアル化するだけの場合、これは機能します。コンバーターを構築するための開発の中止に勝ち、注入されたすべてのプロパティの装飾に勝ります。serializer.TypeNameHandling = TypeNameHandling.Auto; JsonConvert.DefaultSettings()。TypeNameHandling = TypeNameHandling.Auto;
ショーンアンダーソン

39

インターフェイスの複数の実装の逆シリアル化を有効にするには、JsonConverterを使用できますが、属性は使用できません。

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

DTOJsonConverterは、各インターフェイスを具体的な実装にマップします。

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はデシリアライザーにのみ必要です。シリアル化プロセスは変更されていません。Jsonオブジェクトは、具象型名を埋め込む必要はありません。

このSOポストでは、汎用のJsonConverterを使用して、同じソリューションを1歩進めます。


WriteJsonメソッドがserializer.Serializeを呼び出すと、スタックオーバーフローが発生しませんか?コンバーターによってシリアル化されている値でserializeを呼び出すと、コンバーターのWriteJsonメソッドが再帰的に呼び出されるためです
Triynko

CanConvert()メソッドが一貫した結果を返す場合は、そうしないでください。
Eric Boumendil、2014年

3
FullNameタイプを直接比較するだけの場合に、なぜs を比較するのですか?
Alex Zhukovskiy

タイプを比較するだけでも問題ありません。
Eric Boumendil 2016

23

このクラスを使用して、抽象型を実際の型にマッピングします。

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

...そして逆シリアル化するとき:

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

        JsonConvert.DeserializeObject(json, type, settings);

1
私は私の問題を解決する素晴らしい簡潔な答えが本当に好きです。autofacなどは必要ありません。
ベン・パワー

3
これをコンバータークラスの宣言にwhere TReal : TAbstract
追加する価値

1
より完全な場所かもしれませんwhere TReal : class, TAbstract, new()
エリックフィリップス

2
このコンバーターをstructでも使用しました。「TReal:TAbstract」で十分です。皆さんありがとう。
Gildor、

2
ゴールド!スムーズな方法。
SwissCoder

12

ニコラスウェストビーは素晴らしい記事で素晴らしいソリューションを提供しました。

そのようなインターフェースを実装する多くの可能なクラスの1つにJSONを逆シリアル化する場合:

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

カスタムJSONコンバーターを使用できます。

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

また、カスタムコンバーターを使用することを通知するには、JsonConverter属性を使用して "Profession"プロパティを装飾する必要があります。

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

そして、あなたはあなたのクラスをインターフェイスでキャストすることができます:

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

8

あなたが試すかもしれない2つのこと:

try / parseモデルを実装します。

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

または、オブジェクトモデルで実行できる場合は、IPersonとリーフオブジェクトの間に具体的な基本クラスを実装し、それに逆シリアル化します。

1つ目は実行時に失敗する可能性があり、2つ目はオブジェクトモデルを変更する必要があり、出力を最小公分母に均質化します。


私が操作しなければならない規模のため、try / parseモデルは実行できません。頻繁に発生する埋め込みJSONオブジェクトを表すには、数百のベースオブジェクトとさらに数百のスタブ/ヘルパーオブジェクトのスコープを考慮する必要があります。オブジェクトモデルを変更することは問題外ではありませんが、プロパティで具体的な基本クラスを使用すると、ユニットテスト用のアイテムをモックできなくなりますか?それとも何らかの形で後退していますか?
tmesser

IPersonからモックを実装することもできます。Organization.OwnerプロパティのタイプはまだIPersonであることに注意してください。しかし、任意のターゲットを逆シリアル化するには、具体的な型を返す必要があります。タイプ定義を所有しておらず、コードに必要なプロパティの最小セットを定義できない場合、最後の手段はキー/値バッグのようなものです。Facebookのコメント例を使用して、ILocationの実装(1つまたは複数)がどのように見えるかを回答に投稿できますか?それは物事を前進させるのに役立ちます。
mcw

主な目的はあざけることなので、ILocationインターフェースは、実際には単なるLocationオブジェクトのファサードにすぎません。私はちょうどまで働いていた簡単な例は次のようなもの(だろうpastebin.com/mWQtqGnBインタフェースの場合)と、この(pastebin.com/TdJ6cqWV具体的なオブジェクトのため)。
tmesser

そして、次のステップに行くために、これはIpageをは(どのように見えるかの一例であるpastebin.com/iuGifQXp)とページ(pastebin.com/ebqLxzvm)。もちろん問題は、Pageの逆シリアル化は通常は正常に機能しますが、ILocationプロパティに到達するとチョークされることです。
tmesser

では、実際にスクレイピングして逆シリアル化しているオブジェクトについて考えてみましょう。通常、JSONデータは単一の具象クラス定義と一致しているのでしょうか。意味(仮説)に、位置を非直列化オブジェクトの具象型として使用するのに適さないようにする追加のプロパティを持つ「位置」に遭遇しないでしょうか?その場合は、PageのILocationプロパティを "LocationConverter"に関連付けると機能します。そうでない場合、それは、JSONデータが常に固定または一貫した構造(ILocationなど)に準拠していないためです(...続き)
mcw

8

これは重宝しました。あなたもそうかもしれません。

使用例

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

カスタム作成コンバーター

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

Json.NETドキュメント


1
実行可能なソリューションではありません。リストに対応しておらず、どこにでもデコレータ/アノテーションが散在している。
ショーンアンダーソン

5

Oliverによって参照されたConcreteListTypeConverterに興味があるかもしれない人のために、ここに私の試みがあります:

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
私はオーバーライドされたものと混同していCanConvert(Type objectType) { return true;}ます。ハッキリしているようですが、これはどの程度正確に役に立ちますか?私は間違っているかもしれませんが、それは対戦相手に関係なく、彼らが戦いに勝つだろうと経験の浅い小規模の戦闘機に伝えるようなものではありませんか?
Chef_Code

4

それだけの価値があるので、私は結局自分でこれを処理しなければならなくなりました。各オブジェクトには、Deserialize(string jsonStream)メソッドがあります。それのいくつかの断片:

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

この場合、new Thingy(string)は、適切な具象型のDeserialize(string jsonStream)メソッドを呼び出すコンストラクターです。このスキームは、json.NETが処理できる基本ポイントに到達するまで、下降を続けます。

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

などなど。この設定により、ライブラリ自体の大部分をリファクタリングする必要がなく、関係するオブジェクトの数が原因でライブラリ全体が行き詰まるような扱いにくいtry / parseモデルを使用せずに、json.NETが処理できる設定を提供できました。また、特定のオブジェクトでのjsonの変更を効果的に処理でき、オブジェクトが触れるすべてのことについて心配する必要がないことも意味します。これは決して理想的なソリューションではありませんが、単体テストと統合テストで十分に機能します。


4

次のようなautofac設定を想定します。

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

次に、クラスが次のようであるとします。

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

したがって、逆シリアル化でのリゾルバーの使用法は次のようになります。

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

詳細については、http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htmをご覧ください。


私はこれを最善の解決策として賛成投票します。今日、c#web開発者はDIを非常に広く使用しており、これはリゾルバーによるこれらの型変換を処理する集中型の場所としてうまく適合しています。
appletwo

3

インターフェースはすべて定義上抽象的である ため、オブジェクトはIThingyにはなりません。

最初にシリアル化されたオブジェクトは、抽象インターフェースを実装する具体的なタイプのものでした。これと同じコンクリートが必要ですクラスで、シリアル化されたデータを復活させるます。

結果のオブジェクトは、探している抽象インターフェースを実装する何らかのタイプになります。

ドキュメントから、あなたが使用できることが続きます

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

具体的な型についてJSON.NETに通知するために逆シリアル化するとき。


それはまさに私が言及していた1年以上前の投稿です。唯一の主要な提案(カスタムコンバーターの作成)は、私が検討を余儀なくされた規模ではひどく実現可能ではありません。JSON.NETはこの1年で大きく変化しました。クラスとインターフェイスの違いは完全に理解していますが、C#では、インターフェイスから型付けに関するインターフェイスを実装するオブジェクトへの暗黙的な変換もサポートされています。基本的に、JSON.NETにこのインターフェイスを実装するオブジェクトを通知する方法があるかどうかを尋ねています。
tmesser

私があなたに指摘した答えはすべてそこにありました。_type使用する具体的なタイプを通知するプロパティがあることを確認してください。
Sean Kinsey、2011

また、C#が、インターフェイスとして宣言された変数から具象型へのあらゆる種類の「暗黙の」型キャストを、ヒントなしでサポートしていることを強く疑っています。
Sean Kinsey

私がそれを間違って読んでいない限り、_typeプロパティはシリアル化されるJSONにあるはずでした。既にシリアル化したものだけを逆シリアル化している場合は問題ありませんが、ここではそうではありません。私は、その標準に従っていないいくつかのサイトからJSONを取得しています。
tmesser

@YYY-ソースJSONへのシリアライゼーションとデシリアライゼーションの両方を制御しますか?最終的には、シリアル化されたJSONに具象型を、逆シリアル化するときに使用するヒントとして埋め込む必要があるか、実行時に具象型を検出/試行して検出する一種のtry / parseモデルを使用する必要があるためです適切なデシリアライザを呼び出します。
mcw

3

これの私の解決策は、一般的であるので気に入っていますが、次のとおりです。

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

}

変換インスタンス変数をインスタンス化するために、Dictionary <Type、Type>型の引数を取るコンストラクターを追加することにより、明らかにそれをさらに一般的なコンバーターに変換できます。


3

数年後、私は同様の問題を抱えていました。私の場合、入れ子になっているインターフェイスと、実行時に具象クラスを生成するための設定があり、汎用クラスで動作します。

Newtonsoftから返されたオブジェクトをラップするプロキシクラスを実行時に作成することにしました。

このアプローチの利点は、クラスの具体的な実装を必要とせず、ネストされたインターフェースの深さを自動的に処理できることです。詳細については、私のブログをご覧ください。

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

        }
    }



}

使用法:

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

ありがとう!これは、着信jsonに制限を課すことなく動的タイピング(ダックタイピング)を適切にサポートする唯一の回答です。
Philip Pittle 2017

問題ない。私はそこに何もないのを見て少し驚いた。元の例から少し進んだので、コードを共有することにしました。github.com/sudsy/JsonDuckTyper。また、nugetでJsonDuckTyperとして公開しました。あなたがそれを強化したいのを見つけたら、私にPRを送ってください、そして私は義務付けられます。
Sudsy 2017

この領域のソリューションを探していたときに、github.com / ekonbenefits / impromptu-interfaceに出会いました。ドットネットコア1.0をサポートしていないため、私の場合は動作しませんが、動作する可能性があります。
Sudsy 2017

Impromptu Interfaceを試してみましたが、Json.NetはPopulateObject、Impromptu Interfaceによって生成されたプロキシで実行することに満足していませんでした。残念ながら、ダックタイピングをやめました。リフレクションを使用して要求されたインターフェイスの既存の実装を見つけ、それを使用するカスタムJsonコントラクトシリアライザーを作成する方が簡単でした。
フィリップピトル2017

1

このJsonKnownTypesを使用します。これは非常によく似た方法であり、jsonに弁別子を​​追加するだけです。

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

今jsonでオブジェクトをシリアライズする"$type""myClass"値が追加され、デシリアライズに使用されます

Json:

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

0

私のソリューションは、コンストラクターのインターフェース要素を追加しました。

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

     [JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
     public IDetails Details {get; set;}
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.