web-dev-qa-db-ja.com

時々配列と時々オブジェクトのJSONの逆シリアル化

JSON.NETライブラリを使用してFacebookから返されたデータをデシリアライズするときに少し問題があります。

単純なウォールポストから返されるJSONは次のようになります。

{
    "attachment":{"description":""},
    "permalink":"http://www.facebook.com/permalink.php?story_fbid=123456789"
}

写真に対して返されるJSONは次のようになります。

"attachment":{
        "media":[
            {
                "href":"http://www.facebook.com/photo.php?fbid=12345",
                "alt":"",
                "type":"photo",
                "src":"http://photos-b.ak.fbcdn.net/hphotos-ak-ash1/12345_s.jpg",
                "photo":{"aid":"1234","pid":"1234","fbid":"1234","owner":"1234","index":"12","width":"720","height":"482"}}
        ],

すべてがうまく機能し、問題はありません。次のJSONを使用したモバイルクライアントからの単純なウォールポストに遭遇しました。この1つのポストで逆シリアル化が失敗します。

"attachment":
    {
        "media":{},
        "name":"",
        "caption":"",
        "description":"",
        "properties":{},
        "icon":"http://www.facebook.com/images/icons/mobile_app.gif",
        "fb_object_type":""
    },
"permalink":"http://www.facebook.com/1234"

これは私が逆シリアル化しているクラスです:

public class FacebookAttachment
    {
        public string Name { get; set; }
        public string Description { get; set; }
        public string Href { get; set; }
        public FacebookPostType Fb_Object_Type { get; set; }
        public string Fb_Object_Id { get; set; }

        [JsonConverter(typeof(FacebookMediaJsonConverter))]
        public List<FacebookMedia> { get; set; }

        public string Permalink { get; set; }
    }

FacebookMediaJsonConverterを使用しないと、エラーが発生します:JSONオブジェクトを 'System.Collections.Generic.List`1 [FacebookMedia]'型に逆シリアル化できません。 JSONではメディアはコレクションではないため、これは理にかなっています。

同様の問題を説明するこの投稿を見つけたので、このルートをたどってみました: JSONをデシリアライズします。値が配列になることもあります。 ""(空白文字列)

私のコンバーターは次のようになります:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
     if (reader.TokenType == JsonToken.StartArray)
          return serializer.Deserialize<List<FacebookMedia>>(reader);
     else
          return null;
}

これは正常に機能しますが、新しい例外が発生します。

JsonSerializerInternalReader.cs内、CreateValueInternal():オブジェクトのデシリアライズ中の予期しないトークン:PropertyName

Reader.Valueの値は「パーマリンク」です。 JsonToken.PropertyNameのケースがないことをスイッチではっきりと見ることができます。

コンバーターで別の方法を実行する必要がありますか?助けてくれてありがとう。

48
mfanto

JSON.NETの開発者は、プロジェクトのcodeplexサイトを支援することになりました。これが解決策です:

問題は、それがJSONオブジェクトの場合、属性を超えて読み取っていなかったことです。正しいコードは次のとおりです。

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    if (reader.TokenType == JsonToken.StartArray)
    {
        return serializer.Deserialize<List<FacebookMedia>>(reader);
    }
    else
    {
        FacebookMedia media = serializer.Deserialize<FacebookMedia>(reader);
        return new List<FacebookMedia>(new[] {media});
    }
}

Jamesは、上記の方法の単体テストを提供するのにも十分親切でした。

22
mfanto

このケースの処理方法に関する非常に詳細な説明は "カスタムJsonConverterを使用して不正なJSON結果を修正する" にあります。

要約すると、デフォルトのJSON.NETコンバーターを拡張して、

  1. プロパティに問題を注釈します

    [JsonConverter(typeof(SingleValueArrayConverter<OrderItem>))]
    public List<OrderItem> items;
    
  2. コンバーターを拡張して、単一のオブジェクトでも目的のタイプのリストを返す

    public class SingleValueArrayConverter<T> : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            object retVal = new Object();
            if (reader.TokenType == JsonToken.StartObject)
            {
                T instance = (T)serializer.Deserialize(reader, typeof(T));
                retVal = new List<T>() { instance };
            } else if (reader.TokenType == JsonToken.StartArray) {
                retVal = serializer.Deserialize(reader, objectType);
            }
            return retVal;
        }
    
        public override bool CanConvert(Type objectType)
        {
            return true;
        }
    }
    

記事で述べたように、この拡張機能は完全には一般的ではありませんが、リストを取得することに問題がなければ機能します。

30
Camilo Martinez

c#フレームワークのSystem.Runtime.Serialization名前空間を見てください。これにより、目的の場所にすばやく移動できます。

必要に応じて、 this プロジェクトのいくつかのサンプルコードを確認できます(自分の作業をプラグインしようとはしていませんが、私はあなたがやっていることをほぼ正確に終えましたが、別のソースAPIを使用しています。

それが役に立てば幸い。

2
jonezy