Gson解析による複数のJsonObjectを含む複雑なjson

12181 ワード

本明細書に対応する項目はMultiTypeJsonParserであり、項目アドレスhttps://github.com/sososeen09/MultiTypeJsonParser

0前奏


Gsonを使用してjsonを解析するのはよくあるはずですが、ほとんどの場合、Gsonオブジェクトを作成し、jsonと対応するJavaクラスに基づいて解析すればいいのです.
Gson gson = new Gson();
Person person = gson.form(json,Person.class);

しかし、比較的複雑なjsonについては、例えば以下のようにattributes対応のjsonObjectのフィールドが全く異なるので、このときはもっと簡単に上記の方法では解析できません.
{
    "total": 2,
    "list": [
        {
            "type": "address",
            "attributes": {
                "street": "NanJing Road",
                "city": "ShangHai",
                "country": "China"
            }
        },
        {
            "type": "name",
            "attributes": {
                "first-name": "Su",
                "last-name": "Tu"
            }
        }
    ]
}

もちろん、私たちは一歩一歩で解決できないと言っていますが、少し愚かな方法でもいいです.たとえばattributesに対応するjsonObjectを手動で解析し,その同級typeに対応するvalueからこのjsonObjectに対応するJavaクラスがどれであるかを判断し,最後にgson.from()手法を用いてattributesに対応するJavaオブジェクトを解析する.

ListInfoWithType listInfoWithType = new ListInfoWithType();

//   org.json     JSONObject   
JSONObject jsonObject = new JSONObject(TestJson.TEST_JSON_1);
int total = jsonObject.getInt("total");

//   org.json     JSONArray   
JSONArray jsonArray = jsonObject.getJSONArray("list");
Gson gson = new Gson();
List list = new ArrayList<>();

//  
for (int i = 0; i < jsonArray.length(); i++) {
    JSONObject innerJsonObject = jsonArray.getJSONObject(i);
    Class extends Attribute> clazz;
    String type = innerJsonObject.getString("type");
    if (TextUtils.equals(type, "address")) {
        clazz = AddressAttribute.class;
    } else if (TextUtils.equals(type, "name")) {
        clazz = NameAttribute.class;
    } else {
        //         
        continue;
    }
    AttributeWithType attributeWithType = new AttributeWithType();

//  Gson  
    Attribute attribute = gson.fromJson(innerJsonObject.getString("attributes"), clazz);
    attributeWithType.setType(type);
    attributeWithType.setAttributes(attribute);
    list.add(attributeWithType);
}

listInfoWithType.setTotal(total);
listInfoWithType.setList(list);

これによりjson全体の逆シーケンス化が実現するが、この方式は面倒で少しも優雅ではなく、プロジェクトにこのような状況が多く存在すれば、多くの体力労働を繰り返すことになる.どのようにもっと優雅で、もっと汎用的にこのような問題を解決するかは、ネット上で答えが見つからず、Gsonを深く研究するしかなかった.そんな目的でGsonのドキュメントをめくってみると、一言
Gson can work with arbitrary Java objects including pre-existing objects that you do not have source code of.
Gsonは任意のJavaオブジェクトを処理することができます.では、上記のような逆シーケンス化の場合、Gsonもできるはずです.Gsonの文書を検討することにより,JsonDeserializerをカスタマイズすることにより,このようなjsonObjectタイプの異なる解析を実現できることが分かった.
ほとんどの場合,Gsonは直接newで作成されることが知られているが,GsonBuilderというクラスを用いてGsonを生成することもできる.
  Gson gson = new GsonBuilder()
   .registerTypeAdapter(Id.class, new IdTypeAdapter())
   .enableComplexMapKeySerialization()
   .serializeNulls()
   .setDateFormat(DateFormat.LONG)
   .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
   .setPrettyPrinting()
   .setVersion(1.0)
   .create();


GsonBuilderはregisterTypeAdapter()メソッドでターゲットクラスを登録します.ターゲットクラスをシーケンス化または逆シーケンス化すると、登録したtypeAdapterが呼び出され、Gsonのシーケンス化および逆シーケンス化プロセスに手動で介入することができます.
GsonBuilderのregisterTypeAdapte()メソッドの2番目のパラメータはObjectタイプです.つまり、JsonSerializer、JsonDeserializer、InstanceCreator、TypeAdapterの複数のタイプを登録できることを意味します.現在サポートされているタイプはJsonSerializerです.
  public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) 

ドラムを叩いて、ツールクラスを書きました.上の複雑なjsonに対して、10行未満のコードでできて、優雅で汎用的です.
MultiTypeJsonParser multiTypeJsonParser = new MultiTypeJsonParser.Builder()
        .registerTypeElementName("type")
        .registerTargetClass(Attribute.class)
        .registerTargetUpperLevelClass(AttributeWithType.class)
        .registerTypeElementValueWithClassType("address", AddressAttribute.class)
        .registerTypeElementValueWithClassType("name", NameAttribute.class)
        .build();

ListInfoWithType listInfoWithType = multiTypeJsonParser.fromJson(TestJson.TEST_JSON_1, ListInfoWithType.class);

本稿では、JsonDeserializerをカスタマイズすることによって、複雑なタイプのjsonを解析するための一般的なツールクラスを実現する方法を簡単に分析します.後で類似の問題に遭遇する場合、この処理方法は問題を解決する構想を提供することができる.特定のコードとインスタンスは、プロジェクトを表示できます.アイデアにヒントがあれば、コミュニケーションとスターを歓迎します.

1 JsonDeserializer紹介


JsonDeserializerはインタフェースで、使用するときにこのインタフェースを実現し、GsonBuilderで具体的なタイプを登録する必要があります.対応するクラスに逆シーケンス化すると、このカスタムJsonDeserialize()メソッドが呼び出されます.Gson解析の過程をよりよく理解するために,この方法のいくつかのパラメータについて説明する.
public interface JsonDeserializer {
  public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException;
}


1.1 JsonElement


JsonElementはGsonの要素を表します.これは抽象クラスで、JsonObject、JsonArray、JsonPrimitive、JsonNullの4つのサブクラスがあります.1.JsonObjectはname-value型を含むjson文字列を表し、nameは文字列であり、valueは他のタイプのJsonElement要素であってもよい.jsonで「{}」で包まれた全体がJsonObjectです.たとえば
// "attributes"  name,     {}       value,   value    JsonObject
  "attributes": {
                  "first-name": "Su",
                  "last-name": "Tu"
                 }


2.JsonArrayクラスはGsonで配列タイプを表し、1つの配列はJsonElementの集合であり、この集合の各タイプは異なる可能性がある.これは秩序化された集合であり,要素の追加順序が維持されていることを意味する.上記の例ではlist対応の「[]」に包まれたjsonがJsonArrayです.
3.**JsonPrimitive**これは、Javaの8つの基本タイプと対応するパッケージタイプ、Stringタイプを含むjsonの元のタイプと考えられる値です.例えば、上の「first-name」に対応する「Su」はStringタイプのJsonPrimitiveです.
4.JsonNull名前でも推測できますが、これはnull値を表しています.

1.2 Type


TypeはJavaのすべてのタイプの最上位インタフェースであり、そのサブクラスはGenericArrayType、ParameterizedType、Type Variable、WildcardTypeであり、これはjavaである.lang.reflectパッケージの下のクラス.また,我々が最もよく知っているクラスClassもTypeインタフェースを実現している.
一般的にGsonBuilderのregisterType Adapter()を呼び出して登録し、最初のパラメータはClassタイプを使用すればよい.

1.3 JsonDeserializationContext


このクラスは、逆シーケンス中に他のクラスからカスタムJsonDeserialize()メソッドを呼び出すと渡され、Gsonで唯一の実装はTreeType Adapterのプライベートな内部クラスGsonContextImplです.オブジェクトを取得するには、カスタムJsonDeserialize()でJsonDeserializationContextのdeserialize()メソッドを呼び出します.
ただし、JsonDeserializationContextに渡されたjsonがJsonDeserializerのjsonと同じであれば、デッドループ呼び出しを招く可能性があることを覚えておいてください.

2考え方分析


2.1 JavaBeanの作成


やはり一番上のjsonで分析して、listでJsonArrayに対応して、その中の2つのJsonObjectの中で、attributesで対応するJsonObjectフィールドは全く違いますが、統一のためにJavaBeanを書くときに共通の親を設定することができます.それは空ですが.
public class Attribute {
      ...
}

public class AddressAttribute extends Attribute {
    private String street;
    private String city;
    private String country;
...   get/set
}

public class NameAttribute extends Attribute {
    @SerializedName("first-name")
    private String firstname;
    @SerializedName("last-name")
    private String lastname;
...  get/set
}


AttributeというSuperClassを設定するのはGsonBuilderで登録するためだけで、具体的に解析するときはtypeの対応するタイプに応じて対応するClassを見つけます.
 gsonBuilder.registerTypeAdapter(Attribute.class, new AttributeJsonDeserializer());

ここまで来るとtype対応のvalueはきっと具体的なJavaBeanに対応しているに違いないと思います.例えばここで
"address"——AddressAttribute.class
"name"——NameAttribute.class

typeが「address」なら、gsonでAddressAttributeを取ることができます.classと対応するjsonは解析する.
Attribute attribute = gson.form(addressJson,AddressAttribute.class);

2.2 jsonを対応するJavaBeanに正確に変換する方法


親クラスAttributeを登録し、逆シーケンス化でAttributeを解析する必要がある場合、対応するjsonをパラメータとしてカスタムJsonDeserializerにコールバックします.私たちは次の方法で自分の論理を書いて、私たちが必要とするAttributeオブジェクトを得ることができます.
 public Attribute deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)

でも細心の注意を払った友達は気づくはずですが、この時伝えたjsonはそうかもしれません.
{
   "street": "NanJing Road",
   "city": "ShangHai",
   "country": "China"
}

そうかもしれない
{
   "first-name": "Su",
   "last-name": "Tu"
}

AddressAttributeに解析するかNameAttributeに解析するかをどのように知っていますか??
具体的にどのように解析するかを考えてみましょう.typeが対応するvalueを知る必要があります.このtypeはattributesと同級のフィールドで、さっきのようにこのvalueを手に入れることを望んでいないに違いない.
もう一度考えてみましょう.このtypeに対応するvalueが何なのかを知ることができるのはattributesの前のレベルのjsonに違いありません.
{
   "type": "name",
   "attributes": {
                          ...
                 }  
}

では、GsonBuilderにtypeAdapterを登録して、この外層のjsonを解析してもいいですか?もちろんいいです.
 gsonBuilder.registerTypeAdapter(AttributeWithType.class, new AttributeWithTypeJsonDeserializer());

このAttributeWithTypeは外層のjson対応JavaBeanです
public class AttributeWithType {
    private String type;
    private Attribute attributes;
     ...
}

AttributeWithTypeというクラスを逆シーケンス化すると、このtypeに対応するvalueを取得し、このvalueを奥層のAttributeに対応するJsonDeserializerに渡すことができます.これにより、valueが「address」または「name」であることに基づいて、AddresAttributeまたはNameAttributeを逆シーケンス化することができます.

2.3穴がある


前述したように、JsonDeserializationContextを呼び出す方法はデッドサイクルに注意しなければならない.具体的な実践では、JsonDeserializationContextを呼び出す方法はありませんが、デッドサイクルが発生しています.私がこのように使っているからです.
 AttributeWithType attributeWithType = gson.fromJson(json, AttributeWithType.class);


一見問題ないですね.問題はこのgsonにあります.このgsonは解析AttributeWithTypeに登録されているGsonBuilderによって作成された.gson.fromJson()メソッドのjsonはAttributeWithTypeに対応する逆シーケンス化json,gsonである.fromJson()の内部では、AttributeWithType対応のJsonDeserialize()メソッドが再び呼び出され、デッドサイクルが発生します.
デッドサイクルを避ける方法は、GsonBuilderでgsonを新規作成することです.このGsonBuilderはAttributeWithTypeを登録せず、Attributeだけを登録して解析します.

3より一般的な


1.プロジェクトには、外部に個別のtype要素がなく、他の要素と同じJsonObjectに配置された別のフォーマットのjsonも存在する可能性があります.このようなフォーマットは、外層のtypeAdaperを登録する必要がなく、より手間が省けます.
{
    "total": 2,
    "list": [
        {
            "type": "address",
            "street": "NanJing Road",
            "city": "ShangHai",
            "country": "China"
        },
        {
            "type": "name",
            "first-name": "Su",
            "last-name": "Tu"
        }
    ]
}

MultiTypeJsonParser multiTypeJsonParser = new MultiTypeJsonParser.Builder()
        .registerTypeElementName("type")
        .registerTargetClass(Attribute.class)
//         jsonObejct                 ,        Type,     
//        .registerTargetUpperLevelClass(AttributeWithType.class)
        .registerTypeElementValueWithClassType("address", AddressAttribute.class)
        .registerTypeElementValueWithClassType("name", NameAttribute.class)
        .build();

ListInfoWithType listInfoWithType = multiTypeJsonParser.fromJson(TestJson.TEST_JSON_1, ListInfoWithType.class);

2.解析中にMultiTypeJsonParserのBuilderに登録されていないタイプがある場合、解析中に対応するjsonObjectに遭遇するとnullに戻ります.例えば以下のようなjsonでは,「type」に対応する「parents」が登録されていなければ,逆シーケンス化の際にこのjsonが表すオブジェクトはnullである.
 {
        "type": "parents",
        "attributes": {
          "mather": "mi lan",
          "father": "lin ken"
        }
 }

Androidでは逆シーケンスのようなjsonの後、一般的に得られるオブジェクトの設定をリストコントロールに設定し、バックエンドで返されるjsonに以前に登録されていなかったタイプが含まれている場合、プログラムがcrashに至らないように逆シーケンス化されたnullオブジェクトをフィルタリングする必要があり、プロジェクトにはツールクラスListItemFilterがセット内のnullの要素をフィルタリングできるように提供されている.

4結語


このようなタイプの異なるJsonObjectをどのように優雅に解析するかについては、最初は考えが欠けていたが、ネット上で適切なドキュメントを調べられなかった.しかし、Gsonのドキュメントとソースコードを表示することで、自分の理解と分析を通じて、この過程を徐々に完成しました.私の1つの感触は、公式のドキュメントを多く見て、盲目的にソリューションを検索するよりも良いはずです.
コードは最高のドキュメントで、本文は簡単にいくつかの実現構想を紹介しただけで、文の中で貼ったいくつかのコードは説明の便利さのためで、プロジェクトの中のコードといくつかの違いがあるかもしれません.具体的な使用はプロジェクトの例を見ることができます.
もし問題があれば、issueや伝言を歓迎します.もしあなたに役に立つなら、スターを歓迎します.

リファレンス


Gson公式ドキュメント