Json.NETコンバーターを使用してプロパティを逆シリアル化する


88

インターフェイスを返すプロパティを含むクラス定義があります。

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

    public ISomething Thing { get; set; }
}

Json.NETを使用してFooクラスをシリアル化しようとすると、「タイプ 'ISomething'のインスタンスを作成できませんでした。ISomethingはインターフェイスまたは抽象クラスである可能性があります。」のようなエラーメッセージが表示されます。

Somethingデシリアライズ中に使用する具象クラスを指定できるJson.NET属性またはコンバーターはありますか?


ISomethingを取得/設定するプロパティ名を指定する必要があると思います
ram

私が持っています。C#3.5で導入された自動実装プロパティの省略形を使用しています。msdn.microsoft.com/en-us/library/bb384054.aspx
dthrasher 2010

4
ISsomethingタイプではありません。ramは正しいと思いますが、まだプロパティ名が必要です。これがあなたの問題とは関係がないことは知っていますが、上記のコメントから、名前なしでプロパティを指定できる.NETの新機能が欠けていると思いました。
ムース氏2012年

回答:


92

Json.NETでできることの1つは次のとおりです。

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

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

TypeNameHandlingフラグが追加されます$typeJSONにプロパティをします。これにより、Json.NETは、オブジェクトを逆シリアル化する必要がある具象型を知ることができます。これにより、インターフェイスまたは抽象基本クラスを実行しながら、オブジェクトを逆シリアル化できます。

ただし、欠点は、これが非常にJson.NET固有であるということです。$typeデシリアライザのニーズは同様にそれを理解することができるように,,あなたは型情報とそれをシリアル化しているそうだとすれば、完全修飾型になります。

ドキュメント:Json.NETを使用したシリアル化設定


面白い。これをいじる必要があります。いいヒント!
dthrasher 2010年

2
Newtonsoft.Jsonの場合も同様に機能しますが、プロパティは「$ type」です
Jaap


1
を使用する場合は、ここでセキュリティの問題が発生する可能性があることに注意してくださいTypeNameHandling。詳細については、NewtonsoftJsonのTypeNameHandlingの注意を参照してください。
dbc 2017

私は昨日コンバーターに夢中になって苦労しました、そしてこれはずっと良くそしてよりよく理解できました、ありがとう!!!
ホロテニック

52

これは、JsonConverterクラスを使用して実現できます。インターフェイスプロパティを持つクラスがあるとします。

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

JsonConverterは、基になるプロパティのシリアル化と逆シリアル化を担当します。

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

Json.Netを介して逆シリアル化された組織で作業する場合、Ownerプロパティの基になるIPersonはTycoonタイプになります。


非常に素晴らしい。コンバーターを試してみる必要があります。
dthrasher 2010年

4
タグ「[JsonConverter(typeof(TycoonConverter))]」がインターフェイスのリストにある場合でも機能しますか?
zwik 2014

40

前述のように、TypeNameHandling.Objectsオプションを使用してカスタマイズされたJsonSerializerSettingsオブジェクトをJsonConvert.SerializeObject()に渡す代わりに、生成されたJSONが「$ type」プロパティで肥大化しないように、特定のインターフェイスプロパティを属性でマークすることができます。すべてのオブジェクト:

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

鮮やかさ。ありがとう:)
ダレンヤング

5
インターフェイスまたは抽象クラスのコレクションの場合、プロパティは「ItemTypeNameHandling」です。例:[JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)]
Anthony F

これありがとう!
brudert

23

サードパーティのNewtonsoftJsonコンバーターの最新バージョンでは、インターフェイスプロパティに関連する具象型を使用してコンストラクターを設定できます。

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

SomethingがISomethingを実装している限り、これは機能するはずです。また、JSonコンバーターがそれを使用しようとする場合に備えて、デフォルトの空のコンストラクターを配置しないでください。具象型を含むコンストラクターを使用するように強制する必要があります。

PS。これにより、セッターをプライベートにすることもできます。


6
これは屋上から叫ぶべきです!確かに、それは具体的な実装に制約を追加しますが、それを使用できる状況では、他のアプローチよりもはるかに簡単です。
Mark Meuer 2013

3
複数の具象型を持つコンストラクターが複数ある場合でも、それはわかりますか?
テオマンシパヒ2014年

1
この答えは、他の方法でやらなければならない複雑なナンセンスに比べてとてもエレガントです。これは受け入れられた答えでなければなりません。ただし、私の場合の1つの注意点は、コンストラクターの前に[JsonConstructor]を追加して機能させる必要があることでした。これを、具体的なコンストラクターの1つだけで使用すると、(4歳の)問題が解決するのではないかと思います。 @Teomanshipahi
nacitarsevaht18年1

@nacitarsevaht戻って問題を修正できます:)とにかく、それが何であったかさえ覚えていませんが、もう一度見てみると、これは特定の場合に適した解決策です。
テオマンシパヒ2018年

これも使用しますが、具象型をコンストラクターに結合すると、そもそもプロパティのインターフェイスを使用する目的が損なわれるため、ほとんどの場合、変換を好みます。
ゲイブ

19

同じ問題があったので、既知の型引数を使用する独自のコンバーターを思いつきました。

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

デシリアライズとシリアライズの2つの拡張メソッドを定義しました。

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

変換で型を比較および識別する独自の方法を定義できます。私はクラス名のみを使用します。


1
このJsonConverterは素晴らしいです。使用しましたが、この方法で解決したいくつかの問題に直面しました。-オブジェクトの階層が深いため、Populateの代わりにJsonSerializer.CreateDefault()を使用しました。-リフレクションを使用してコンストラクターを取得し、Create()メソッドでインスタンス化します
Aurel

3

通常TypeNameHandling、DanielTによって提案されたように、私は常にソリューションを使用していましたが、ここで受信JSONを制御できなかった場合(したがって、$typeプロパティが含まれていることを確認できません)、明示的に指定できるカスタムコンバーターを作成しました具体的なタイプ:

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

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

ソースコードと概要は、このブログ投稿で入手できます。


1
これは素晴らしい解決策です。乾杯。
ジョンメッタ2018

2

@DanielT。が上で示した例を完成させたかっただけです。

このコードを使用してオブジェクトをシリアル化する場合:

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

jsonを逆シリアル化するコードは次のようになります。

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

これは、TypeNameHandlingフラグを使用するときにjsonが準拠する方法です。ここに画像の説明を入力してください


-5

これと同じことを考えたのですが、できないのではないかと思います。

このように見てみましょう。JSon.netにデータの文字列と、逆シリアル化する型を渡します。それがISomethingに達したときにJSON.netは何をしますか?ISomethingはオブジェクトではないため、新しいタイプのISomethingを作成することはできません。また、ISomethingを実装するオブジェクトを作成することもできません。これは、ISomethingを継承する可能性のある多くのオブジェクトのどれを使用すべきかについての手がかりがないためです。インターフェイスは、自動的にシリアル化できますが、自動的に逆シリアル化することはできません。

私がすることは、ISomethingを基本クラスに置き換えることを検討することです。それを使用すると、あなたが探している効果を得ることができるかもしれません。


1
「箱から出して」は機能しないことに気づきました。しかし、具体的なクラスを提供するために使用できる「[JsonProperty(typeof(SomethingBase))]」のような属性があるかどうか疑問に思いました。
dthrasher 2010

では、上記のコードでISomethingの代わりにSomethingBaseを使用してみませんか?インターフェイスは特定のクラスとの通信「インターフェイス」を定義するだけなので、シリアル化ではインターフェイスを使用すべきではないため、これも間違った方法で見ていると主張することができます。抽象クラスのシリアル化と同様に、インターフェイスのシリアル化は技術的に意味がありません。ですから、それは「行われるべきである」一方で、「行われるべきではない」と私は主張します。
ティモシーボールドリッジ2010

Newtonsoft.Json.Serialization名前空間のクラスのいずれかを見たことがありますか?特にJsonObjectContractクラス?
johnny 2010

-9

これはScottGuによって書かれた記事への参照です

それに基づいて、私は役立つと思ういくつかのコードを書きました

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

そして、これはあなたがそれを呼ぶ方法です

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}

正しく理解していれば、JSONシリアル化のインターフェースを実装する具象クラスを指定する必要はないと思います。


1
サンプルでは、​​.NETFrameworkのクラスであるJavaScriptSerializerを使用しています。シリアライザーとしてJson.NETを使用しています。codeplex.com/Json
dthrasher

3
元の質問には言及していませんが、Json.NETはそこで明示的に言及されていました。
オリバー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.