Gsonまたは「There and Back」

最近、JavaオブジェクトをJSONテキスト形式(シリアル化)および逆変換(逆シリアル化)に変換するように設計されたGoogle Gsonライブラリを使用する必要がありました。 多くの場合、Gsonを使用する場合、標準ライブラリ設定で十分ですが、変換プロセスをカスタマイズする必要がある場合(私の場合を含む)があります。



Gsonを使用した後、このチュートリアルを作成することにしました。このチュートリアルでは、例を使用してライブラリを操作する原理を説明します。 投稿は比較的長いことが判明しましたが、ストーリーの論理的一貫性のために分割したくありません。



最初に、サブジェクト領域を選択する必要があります。 どういうわけか、ノームの切り離しの考えが頭に浮かぶのかわかりません。 実際、なぜですか?







はい、記事に含まれるすべてのコードはGitHubで見つけることができます: https : //github.com/treble-snake/gson.dwarves

クラス図以外の画像は、 http: //www.javacreed.comのGsonに関する一連の記事から引用されています



はじめに



ドワーフについて


だから、「分隊」でそれは明らかです-これはある種のノームです。 しかし、ドワーフ自身はどうですか? ノームを特徴付ける最も重要な詳細は、もちろん、ひげです。 gnomeのひげの特徴と分類は長い間説明できますが、簡単にするために、gnomeに口ひげがあるかどうか、ひげがあるかどうか、およびそれらの色を定義します。 さらに、名前と年齢-それらなしで。 個人的な何かを追加し、小人が昼食を食べたと言います。 そして最後に、武器。 gnomeは多くの武器を持つことができ、シンプルにすることも、独自の名前と起源を持つユニークにすることもできます。



結果は次のようになります。





ドメインクラスの説明
簡潔にするために、1つのリストですべてのクラスを提供します。

public class DwarvesBand { List<Dwarf> dwarves = new LinkedList<>(); // getters & setters } public class Dwarf { private String name; private FacialHair facialHair; private List<Weapon> weapons = new LinkedList<>(); private String lunch; private int dwarfAge; public Dwarf() { } public Dwarf(String name, int dwarfAge) { this.name = name; this.dwarfAge = dwarfAge; } // getters & setters } /** *     */ public class FacialHair { private boolean haveBeard; private boolean haveMustache; private String color; public FacialHair(boolean haveBeard, boolean haveMustache, String color) { this.haveBeard = haveBeard; this.haveMustache = haveMustache; this.color = color; } // getters & setters } public class Weapon { private String type; public Weapon() { // do nothing } public Weapon(String type) { this.type = type; } // getters & setters } public class UniqueWeapon extends Weapon { private String name; private String origin; public UniqueWeapon() { super(); } public UniqueWeapon(String type, String name, String origin) { super(type); this.name = name; this.origin = origin; } // getters & setters }
      
      









3人の参加者を追加してgnome会社を初期化します( すべてのキャラクターは架空のものであり、偶然の一致はランダムです )。

 public class BandUtil { public static DwarvesBand createBand() { DwarvesBand company = new DwarvesBand(); Dwarf tmpDwarf; tmpDwarf = new Dwarf("Orin", 90); tmpDwarf.setLunch("Ale with chicken"); tmpDwarf.setFacialHair(new FacialHair(true, true, "black")); tmpDwarf.addWeapon(new UniqueWeapon("sword", "Slasher", "Gondolin")); tmpDwarf.addWeapon(new UniqueWeapon("shield", "Oaken Shield", "Moria")); tmpDwarf.addWeapon(new Weapon("dagger")); company.addDwarf(tmpDwarf); tmpDwarf = new Dwarf("Kori", 60); // no lunch :( tmpDwarf.setFacialHair(new FacialHair(false, true, "red")); tmpDwarf.addWeapon(new Weapon("mace")); tmpDwarf.addWeapon(new Weapon("bow")); company.addDwarf(tmpDwarf); tmpDwarf = new Dwarf("Billy Bob", 45); tmpDwarf.setLunch("Ale with chicken and potatoes, tea with some cakes"); tmpDwarf.setFacialHair(new FacialHair(false, false, "")); company.addDwarf(tmpDwarf); return company; } }
      
      





あそこ



デフォルトで


そのため、私たちはJSON形式でノームに関する情報を取得したいと考えています。 最も簡単な方法を試してみましょう-同じ名前のクラスのインスタンスを作成してtoJson()



メソッドを呼び出すことにより、Gsonライブラリの標準パラメーターを使用します。

 DwarvesBand band = BandUtil.createBand(); Gson gson = new GsonBuilder() .setPrettyPrinting() .create(); String json = gson.toJson(band);
      
      





実際には、 Gson



クラスのインスタンスもnew



演算子を使用して作成Gson



ますが、出力JSONはフォーマットされていないため、アプリケーション間でデータを交換するのに適しています(形成が速く、重量が少なくなります)が、人間の知覚には適していません。 したがって、特別なGsonBuilderを使用してsetPrettyPrinting()



メソッドを呼び出し、出力JSONを次の形式で表示できるようにしました。

デフォルトでシリアライズ後のJSON
 { "dwarves": [ { "name": "Orin", "facialHair": { "haveBeard": true, "haveMustache": true, "color": "black" }, "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, { "name": "Oaken Shield", "origin": "Moria", "type": "shield" }, { "type": "dagger" } ], "lunch": "Ale with chicken", "dwarfAge": 90 }, { "name": "Kori", "facialHair": { "haveBeard": false, "haveMustache": true, "color": "red" }, "weapons": [ { "type": "mace" }, { "type": "bow" } ], "dwarfAge": 60 }, { "name": "Billy Bob", "facialHair": { "haveBeard": false, "haveMustache": false, "color": "" }, "weapons": [], "lunch": "Ale with chicken and potatoes, tea with some cakes", "dwarfAge": 45 } ] }
      
      







さて、あなたはすでにこれで作業することができます、しかし、あなたがそれについて考えるならば、いくつかのコメントがあります:

  1. 「dwarfAge」とはどのような愚かなプロパティ名ですか? そして、私たちがノームについて話していることは明らかです。 それはちょうどその年齢がずっと良く見えていただろうということです。
  2. おそらく、昼食に関する情報はそれほど重要ではありません。 彼女なしでもできます。
  3. あごひげの説明はなんとなく乾いているので、これは許されません。 完全な文、つまり「赤ひげと口ひげ」または「黒口ひげ」などの行で記述する必要があります。
  4. 従来の武器に対して単一の「タイプ」プロパティを持つオブジェクトを取得する必要があるのはなぜですか? 費用はわずか1行です。


すべてのコメントを考慮に入れると、次の形式でgnomeに関する情報を表示できます。

  { "name": "Orin", "facialHair": "Black beard and mustache", "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, ... , "dagger" ], "age": 90 }
      
      







注釈


Gsonは、シリアル化を設定するための便利な注釈を提供します。 彼らが私たちを助けることができるかどうか見てみましょう。



最初の問題で-はい、 SerializedNameアノテーションをrespに追加することで、プロパティの出力名を変更できます。 クラスフィールド。 つまり、そうすることで:



 @SerializedName("age") private int dwarfAge;
      
      





「dwarfAge」ではなく「age」という名前のプロパティを取得します。



すでに悪くありません、先に進みましょう。 lunch



フィールドを除外します。 まず、これにtransientキーワードを追加することでこれを行うことができます。この場合、シリアル化中にフィールドは考慮されません。 しかし、これが正しい方法であるという事実ではありません。 ここで昼食に関する情報が必要ないという事実は、他のシリアル化では必要ないという意味ではありません。

もう1つの方法は、 Exposeアノテーションを使用することです。 GsonBuilder.excludeFieldsWithoutExposeAnnotation()メソッドと連携してのみ機能します。このメソッドは、Exposeアノテーションを持たないすべてのフィールドの処理から除外します。 ただし、1つのフィールドを除外するには、他のすべてのフィールドに注釈を追加する必要があります。 便利すぎませんか?



カスタムシリアライザー


より柔軟な方法は、特定のタイプのオブジェクトをシリアル化する独自のクラスを作成することです。 これを行うには、 JsonSerializer <T>インターフェイスを実装する必要があります。Tは処理されたオブジェクトのタイプです。 単一のインターフェースのserialize()



メソッドを考えてください:

 JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context)
      
      





次の3つのパラメーターが必要です。



メソッドの戻りデータ型はJsonElement



です。 これは、次の図に示す4つの実装を持つ抽象クラスです。





次の図は、タイプの組み合わせの例を示しています。





ノームをシリアル化する時間


それで、十分な理論、最後にシリアライズしましょう!

まず、カスタム処理が必要なデータ型はいくつ必要ですか?

まず、もちろん、これはgnome- Dwarf



を記述するクラスです。

第二に、あごひげと口ひげのクラスはFacialHair



です。

Weapon



、特にUniqueWeapon



もここに起因しますが、今のところはデフォルトの処理に任せましょう。



したがって、 JsonSerializer



2つの実装が必要JsonSerializer



。 それらは非常によく似ています:

 public class DwarfSerializer implements JsonSerializer<Dwarf> { @Override public JsonElement serialize(Dwarf src, Type typeOfSrc, JsonSerializationContext context) { //  ! return null; } } public class FacialHairSerializer implements JsonSerializer<FacialHair> { @Override public JsonElement serialize(FacialHair src, Type typeOfSrc, JsonSerializationContext context) { //    ! return null; } }
      
      





Gsonがノームを処理するときにシリアライザーを使用するには、次のようにGsonBuilder



クラスのregisterTypeAdapter()



メソッドを使用してregisterTypeAdapter()



する必要があります。



 Gson gson = new GsonBuilder() .setPrettyPrinting() .registerTypeAdapter(Dwarf.class, new DwarfSerializer()) .registerTypeAdapter(FacialHair.class, new FacialHairSerializer()) .create();
      
      







ひげと口ひげ


ひげと口ひげの処理から始めることに気付きます。 以下に完全なコードを示します。詳細については、以下で説明します。

 public class FacialHairSerializer implements JsonSerializer<FacialHair> { @Override public JsonElement serialize(FacialHair src, Type typeOfSrc, JsonSerializationContext context) { if (!src.isHaveBeard() && !src.isHaveMustache()) return new JsonPrimitive("is he really a dwarf?"); List<String> list = new LinkedList<String>(); if (src.isHaveBeard()) { list.add("beard"); } if (src.isHaveMustache()) { list.add("mustache"); } return new JsonPrimitive( new StringBuilder(src.getColor()) .append(" ") .append(StringUtils.join(list, " and ")) .toString() ); } }
      
      





すべてが非常に簡単です。 ひげと口ひげに関する情報を1行に減らすため、serialize()メソッドの結果は、目的の行を含むJsonPrimitive



オブジェクトになります。

たとえば、gnomeにひげも口ひげもない場合、gnome属に対する態度を疑うことができます。

 if (!src.isHaveBeard() && !src.isHaveMustache()) return new JsonPrimitive("is he really a dwarf?");
      
      





それ以外の場合、かなり簡単なアルゴリズムを使用して、ソースデータから必要な型の文字列を取得し、それに基づいてJsonPrimitive



インスタンスを作成します。 そして、もちろん、入力オブジェクトと髪の色は、記事の教育目的にとってまったく重要ではないチェックでコードを複雑にしないために、常に初期化されると考えてみましょう。



ノーム自身


次に、gnomeの処理全体を実装します(チェックも省略します)。

 public class DwarfSerializer implements JsonSerializer<Dwarf> { @Override public JsonElement serialize(Dwarf src, Type typeOfSrc, JsonSerializationContext context) { JsonObject result = new JsonObject(); result.addProperty("name", src.getName()); result.addProperty("age", src.getDwarfAge()); result.add("facialHair", context.serialize(src.getFacialHair())); JsonArray weapons = new JsonArray(); result.add("weapons", weapons); for(Weapon weapon : src.getWeapons()) { weapons.add( weapon instanceof UniqueWeapon ? context.serialize(weapon) : new JsonPrimitive(weapon.getType()) ); } return result; } }
      
      





このコードを部分的に解析しましょう。 結果としてJSONオブジェクトを取得する必要があるため、適切なタイプの変数を作成します。

 JsonObject result = new JsonObject();
      
      





次に、 addProperty()メソッドを使用して、プリミティブ型のデータをオブジェクトに入力します(中間のJsonPrimitive



オブジェクトを作成せずに)。 メソッドに2つのパラメーターを渡します。1つ目はキー、つまりJSONオブジェクトのプロパティの名前、2つ目は実際にはこのプロパティの値です。 ここでは、「dwarfAge」の代わりに「age」プロパティの名前を設定し、結果からランチ情報を除外します-結果オブジェクトに追加しません。

 result.addProperty("name", src.getName()); result.addProperty("age", src.getDwarfAge());
      
      





次に、ひげに関するデータを追加する必要があります。 これを行うには、コンテキストserialize()



メソッドを使用します。前述のように、コンテキストは登録済みのFacialHair



ため、 FacialHair



クラスにはFacialHairSerializer



FacialHair



適用されます。 目的のプロパティ名を指定して、 add()メソッドを使用して、結果のJsonElement



をオブジェクトに追加します。

 result.add("facialHair", context.serialize(src.getFacialHair()));
      
      





ノームの武器に関する情報を追加することだけが残っています。 武器のシンボリックキーがないため、 JsonArray



インスタンスを作成してそれらを保存し、同じadd()メソッドを使用してオブジェクトに追加します。

 JsonArray weapons = new JsonArray(); result.add("weapons", weapons);
      
      





次に、作成した配列に要素を入力する必要があります。 JsonArray



クラスにはadd()メソッドもありますが、 JsonElement



型のパラメーターは1つだけで、論理的です。この場合、キーは必要ありません。 従来の武器を追加する場合、文字列に基づいてJsonPrimitive



を作成し、コンテキストを使用して一意の武器をシリアル化します。 この場合、 UniqueWeapon



クラスのハンドラーを登録しなかったため、標準のシリアル化メカニズムが機能します。

 weapons.add( weapon instanceof UniqueWeapon ? context.serialize(weapon) : new JsonPrimitive(weapon.getType()) );
      
      





結果


最後に、意図した目的のために労働の成果を使用します。

 DwarvesBand band = BandUtil.createBand(); Gson gson = new GsonBuilder() .setPrettyPrinting() .registerTypeAdapter(Dwarf.class, new DwarfSerializer()) .registerTypeAdapter(FacialHair.class, new FacialHairSerializer()) .create(); String json = gson.toJson(band);
      
      





取得したものを確認します。

出力JSON
 { "dwarves": [ { "name": "Orin", "age": 90, "facialHair": "black beard and mustache", "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, { "name": "Oaken Shield", "origin": "Moria", "type": "shield" }, "dagger" ] }, { "name": "Kori", "age": 60, "facialHair": "red mustache", "weapons": [ "mace", "bow" ] }, { "name": "Billy Bob", "age": 45, "facialHair": "is he really a dwarf?", "weapons": [] } ] }
      
      







最後のタッチ


変更したいのは、所有しているすべてのノームが「ドワーフ」プロパティに格納されている配列の要素であるということだけです。 これはどういうわけか堅実で冗長です。ノームについて話していることはわかっていますよね。 各gnomeをJSONオブジェクトの個別のプロパティとします。キーはgnomeの名前です。 例:

 { "Kori": { "age": 60, "facialHair": "red mustache", "weapons": [ ... ] }, ... }
      
      





ほとんどの場合、あなた自身がこの最終的なタッチに命を吹き込むために何をする必要があるか想像できます。 ただし、念のため:

実装
1. gnome会社全体にシリアライザーを追加します。

 public class DwarvesBandSerializer implements JsonSerializer<DwarvesBand> { @Override public JsonElement serialize(DwarvesBand src, Type typeOfSrc, JsonSerializationContext context) { JsonObject result = new JsonObject(); for(Dwarf dwarf : src.getDwarves()) { result.add(dwarf.getName(), context.serialize(dwarf)); } return result; } }
      
      







2.次の行を削除して、gnome DwarfSerializer



DwarfSerializer



クラス)から名前情報を削除します。

 result.addProperty("name", src.getName());
      
      







3. GsonBuilder



クラスのregisterTypeAdapter()



メソッドへの呼び出しを追加して、分隊GsonBuilder



registerTypeAdapter()



ます。

 .registerTypeAdapter(DwarvesBand.class, new DwarvesBandSerializer())
      
      







そして、ノームの会社に必要なデータ形式を取得しました。

じゃあ!
 { "Orin": { "age": 90, "facialHair": "black beard and mustache", "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, { "name": "Oaken Shield", "origin": "Moria", "type": "shield" }, "dagger" ] }, "Kori": { "age": 60, "facialHair": "red mustache", "weapons": [ "mace", "bow" ] }, "Billy Bob": { "age": 45, "facialHair": "is he really a dwarf?", "weapons": [] } }
      
      







青い霧、白い霧のために安全に行くことができます!



戻る



JSONの冒険から戻ったgnomeチームは、自然に居心地の良いJavaオブジェクトに変身したいと思っています。 逆変換、つまり逆シリアル化のために、GsonにはfromJson()



メソッドがあります。 2つのパラメーターを取ります。いくつかの形式のデータ(使用するString



を含む)と返される結果の型です。 ただし、以下に示すように、単にGsonオブジェクトを作成してこのメ​​ソッドを呼び出すと、 DwarvesBand



の空のリストを持つDwarvesBand



クラスのインスタンスが取得されます。

 DwarvesBand dwarvesBand = new Gson().fromJson(json, DwarvesBand.class);
      
      





変換には独自のアルゴリズムを使用し、デフォルトのGsonはフォーマットの処理方法を知らないため、これは自然なことです。 したがって、まったく同じ方法で、特殊なデシリアライザーを作成し、ライブラリーにノームに関する情報を処理するために使用する必要があることを伝える必要があります。 すでに推測したかもしれませんが、それらを作成するには、 JsonDeserializer <T>インターフェースとその唯一のdeserialize ()メソッドを実装する必要があります。

 T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      
      





使用可能なパラメーター:



返されるデータ型はパラメーター化されます。

さあ始めましょう!



ボロローダ!


小さく始めましょう。 あごひげと口ひげに関するデータを復元します。 完全なデシリアライザーコード:

 public class FacialHairDeserializer implements JsonDeserializer<FacialHair> { @Override public FacialHair deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { FacialHair hair = new FacialHair(); String data = json.getAsString(); List<String> parts = Arrays.asList(data.split(" ")); if(parts.contains("beard")) hair.setHaveBeard(true); if(parts.contains("mustache")) hair.setHaveMustache(true); if(hair.isHaveBeard() || hair.isHaveMustache()) hair.setColor(parts.get(0)); return hair; } }
      
      





はい、良い方法で、入力データをより注意深くチェックする価値がありますが、例のコードを複雑にしないためにそれが正しいことは当然だと思います。

このメソッドの最も重要な行:

 String data = json.getAsString();
      
      





getAsString()メソッドは、有効な文字列を含むJsonPrimitive



型の要素、またはJsonArray



型のそのような要素を1つだけ含むJsonPrimitive



適用される場合、 JsonElement



の内容を文字列に変換します。 そうでない場合、メソッドは例外をスローします。 フォームgetAs{JavaType}()



すべてのメソッドは同様に機能します。

入力として文字列をJsonPrimitive



を取得するので、これをチェックしません( isJsonPrimitive()



メソッドを使用できます)。 取得したデータをさらに処理するのは簡単であり、私たちはそのデータにとどまりません。



ノーム


ノームのデータを復元する時が来ました。 このようにします:

 public class DwafDeserializer implements JsonDeserializer<Dwarf> { @Override public Dwarf deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); Dwarf dwarf = new Dwarf(); dwarf.setDwarfAge(jsonObject.get("age").getAsInt()); dwarf.setFacialHair((FacialHair) context.deserialize(jsonObject.get("facialHair"), FacialHair.class)); JsonArray weapons = jsonObject.getAsJsonArray("weapons"); for(JsonElement weapon : weapons) { if(weapon.isJsonPrimitive()) { dwarf.addWeapon(new Weapon(weapon.getAsString())); } else { dwarf.addWeapon((UniqueWeapon) context.deserialize(weapon, UniqueWeapon.class)); } } return dwarf; } }
      
      





繰り返しますが、簡潔にするために一部のチェックは省略されています。 部分的に分析します。

gnomeに関する情報はJsonObject



として表されることがJsonObject



ているため、入力をチェックせずにこのタイプに変換します。

 JsonObject jsonObject = json.getAsJsonObject();
      
      





年齢は整数型であるため、最初にget()



メソッドをJsonElement



年齢をJsonElement



メソッドは、指定された「age」プロパティの値を持つJsonElement



を返し、次にgetAsInt()



メソッドをgetAsInt()



ます。

 dwarf.setDwarfAge(jsonObject.get("age").getAsInt());
      
      





context.deserialize()



を使用して、ひげデータをFacialHair



タイプのオブジェクトに復元します。 覚えているように、コンテキストはひげ情報を処理するために特別なデシリアライザーを使用する必要があることを認識しています。

 dwarf.setFacialHair((FacialHair) context.deserialize(jsonObject.get("facialHair"), FacialHair.class));
      
      





Json配列の形式で「武器」プロパティの値をすぐに取得します。 最初にget(「武器」)メソッドでJsonElement



を取得し、次にisJsonArray()



メソッドを使用して配列の型を確認し、次にisJsonArray()



メソッドを使用して配列に変換します。 しかし、私たちはノームとその入力の形式を信じています。

 JsonArray weapons = jsonObject.getAsJsonArray("weapons");
      
      





武器のデータを復元するために、配列をウォークスルーします。

 for(JsonElement weapon : weapons) { if(weapon.isJsonPrimitive()) { dwarf.addWeapon(new Weapon(weapon.getAsString())); } else { dwarf.addWeapon((UniqueWeapon) context.deserialize(weapon, UniqueWeapon.class)); } }
      
      





各要素について、 JsonPrimitive



タイプかどうかを確認します。 従来の武器は、このタイプに対応する単純な線で記述されていることを覚えています。 この場合、従来の武器のインスタンスを作成し、 getAsString()



メソッドを使用してそのタイプを取得します。 そうでなければ、私たちはユニークな武器を扱っています。 標準のGsonメカニズムを使用したコンテキストを使用して処理しました。 context.deserialize()



を使用して、同じことを実行します。



何かが足りないことに気づいた? そして、「何か」だけでなく、ノームの名前も! この重要な詳細を追加してgnome情報の回復を完了するには、最後のデシリアライザーに進みましょう。



デタッチメント


最後に、gnomeチーム全体のハンドラーを追加します。

 public class DwarvesBandDeserializer implements JsonDeserializer<DwarvesBand> { @Override public DwarvesBand deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { DwarvesBand result = new DwarvesBand(); JsonObject jsonObject = json.getAsJsonObject(); for(Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) { Dwarf dwarf = context.deserialize(entry.getValue(), Dwarf.class); dwarf.setName(entry.getKey()); result.addDwarf(dwarf); } return result; } }
      
      





gnome処理と同様にJsonObject



入力をJsonObject



型にJsonObject



ます。 JsonObject



Map<String, JsonElement>



として解釈できることは前に述べたのをJsonObject



ますか? Map



と同様に、 JsonObject



は、多くのキーと値の要素を返すentrySet()



メソッドがあります。 彼の助けを借りて、ノームに関するすべての記録を一巡します。

要素の値は、名前を除く、gnomeに関するすべての情報です。 コンテキストを使用してこの情報を逆シリアル化し、Dwarfクラスのインスタンスを取得します。

 Dwarf dwarf = context.deserialize(entry.getValue(), Dwarf.class);
      
      





空白のままになっている名前は、要素キーに含まれています。 これをオブジェクトに書き込むと、できればgnomeに関する情報が完全に復元されます!

 dwarf.setName(entry.getKey());
      
      







ホームスイートホーム


焼きたてのデシリアライザーを登録することは残っており、「There and Back」の旅を始めることができます。 登録は、シリアライザーの登録とまったく同じです。

 Gson gson = new GsonBuilder() .registerTypeAdapter(DwarvesBand.class, new DwarvesBandDeserializer()) .registerTypeAdapter(FacialHair.class, new FacialHairDeserializer()) .registerTypeAdapter(Dwarf.class, new DwafDeserializer()) .create();
      
      







テストするには、最初にノームの会社をJson文字列に変換し、次に元に戻し、明確にするために、標準のGsonメカニズムを使用して取得したJsonオブジェクトとして結果を出力します。 あなたは誰も忘れられず、何も忘れられないことを確認することができます、すべてのノームは安全で健全に戻りました!

検証コード
 DwarvesBand company = BandUtil.createBand(); Gson gson; gson = new GsonBuilder() .registerTypeAdapter(Dwarf.class, new DwarfSerializer()) .registerTypeAdapter(FacialHair.class, new FacialHairSerializer()) .registerTypeAdapter(DwarvesBand.class, new DwarvesBandSerializer()) .create(); String json = gson.toJson(company); gson = new GsonBuilder() .registerTypeAdapter(DwarvesBand.class, new DwarvesBandDeserializer()) .registerTypeAdapter(FacialHair.class, new FacialHairDeserializer()) .registerTypeAdapter(Dwarf.class, new DwafDeserializer()) .create(); DwarvesBand bandIsBack = gson.fromJson(json, DwarvesBand.class); gson = new GsonBuilder() .setPrettyPrinting() .create(); System.out.println(gson.toJson(bandIsBack));
      
      







結果
 { "dwarves": [ { "name": "Orin", "facialHair": { "haveBeard": true, "haveMustache": true, "color": "black" }, "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, { "name": "Oaken Shield", "origin": "Moria", "type": "shield" }, { "type": "dagger" } ], "dwarfAge": 90 }, { "name": "Kori", "facialHair": { "haveBeard": false, "haveMustache": true, "color": "red" }, "weapons": [ { "type": "mace" }, { "type": "bow" } ], "dwarfAge": 60 }, { "name": "Billy Bob", "facialHair": { "haveBeard": false, "haveMustache": false, "color": "" }, "weapons": [], "dwarfAge": 45 } ] }
      
      







両方向に



そのため、「There」(JavaからJSONへ)と「Back」(JSONからJavaへ)の旅を調べました。 JsonElement



とデJsonElement



JsonElement



親切に提供してくれたJsonElement



ようなオブジェクトの中間層で作業しました。



また、非常に便利ですが、オーバーヘッドにつながります。 Gsonは、中間層を削除することでパフォーマンスの利便性を犠牲にする機会を与えてくれます。これを行うには、カスタム変換にJsonSerializer + JsonDeserializerのペアではなく、両方向に変換するように設計されTypeAdapter <T>クラスの実装を使用します。私たちが最も興味を持っているのは、このクラスの2つの抽象メソッド- write()



read()



です。これらは、カスタム変換に責任があります。-シリアルwrite()



化、およびread()



-シリアル化解除。



デフォルトで、gnomeの武器を独自のデバイスに投げたことを覚えていますか?この不正を修正しましょう。 「ゴンドラの斬撃兵」のような行に武器の名前と起源を組み合わせます。そして、ささいなことをしないために、私たちは作成しますTypeAdapter



ユニークなアイテムだけでなく、武器のリスト全体に対して。クラスは次のようになります。

 public class WeaponsTypeAdapter extends TypeAdapter<List<Weapon>> { @Override public void write(JsonWriter out, List<Weapon> value) throws IOException { // Java → JSON } @Override public List<Weapon> read(JsonReader in) throws IOException { // JSON → Java return null; } }
      
      





さて、古いスキームによれば、メソッドを呼び出すことにより、武器リストの新しいハンドラーをGsonに通知する必要があり.registerTypeAdapter()



ます。ただし、障害があります。-メソッドの最初のパラメータは、私たちが通常のリストを販売し、ハンドラが登録されているデータ型で、GNOMEの武器List<Weapon>



また、他のすべてのリストがTypeAdapterによって処理されることは望ましくありません。パラメータ化された型を渡して、武器のリスト専用であることを何らかの形で示す必要があります。これを行うために、Gsonは特別なトリッキーなクラス-TypeToken <T>を使用します。これにより、次のように必要なパラメーター化された型を取得できます。

 Type weaponsListType = new TypeToken<List<Weapon>>(){}.getType();
      
      





実際、TypeToken



匿名クラスによってパラメーター化されたクラスを特別に継承しているためgetGenericSuperclass()



メソッドの親をパラメーター化する型を取得できます。この場合、親をパラメーター化する型はours List<Weapon>



です。少々混乱しますが、別の方法で、残念ながら何もありません。たとえば、この記事では、汎用クラスのパラメーターの取得について詳しく読むことができます

よく、さらに-通常どおり:

 Type weaponsListType = new TypeToken<List<Weapon>>(){}.getType(); Gson gson = new GsonBuilder() .setPrettyPrinting() .registerTypeAdapter(Dwarf.class, new DwarfSerializerWithTypeAdapter()) .registerTypeAdapter(FacialHair.class, new FacialHairSerializer()) .registerTypeAdapter(DwarvesBand.class, new DwarvesBandSerializer()) .registerTypeAdapter(weaponsListType, new WeaponsTypeAdapter()) .create();
      
      





gnomeのシリアル化とシリアル化解除のコードを変更するだけで、処理された値のタイプを示すコンテキストに武器の処理の制御を渡します。

 public class DwarfSerializerWithTypeAdapter implements JsonSerializer<Dwarf> { public JsonElement serialize(...) { ... Type weaponsType = new TypeToken<List<Weapon>>(){}.getType(); result.add("weapons", context.serialize(src.getWeapons(), weaponsType)); ... } } public class DwafDeserializerWithTypeAdapter implements JsonDeserializer<Dwarf> { public Dwarf deserialize(...) { ... Type weaponsType = new TypeToken<List<Weapon>>(){}.getType(); List<Weapon> weapons = context.deserialize(jsonObject.getAsJsonArray("weapons"), weaponsType); dwarf.addWeapons(weapons); ... } }
      
      





以上で、アダプターは接続されました。ああ、それを実現するために残っています。いつものように、ネタバレの下には完全なコードがあります。これについては、以下で詳細に分析します。

完全なTypeAdapterコード
 public class WeaponsTypeAdapter extends TypeAdapter<List<Weapon>> { @Override public void write(JsonWriter out, List<Weapon> value) throws IOException { out.beginArray(); for (Weapon weapon : value) { if (weapon instanceof UniqueWeapon) { UniqueWeapon uWeapon = (UniqueWeapon) weapon; out.beginObject(); out.name("name") .value(uWeapon.getName() + " from " + uWeapon.getOrigin()); out.name("type") .value(uWeapon.getType()); out.endObject(); } else { out.value(weapon.getType()); } } out.endArray(); } @Override public List<Weapon> read(JsonReader in) throws IOException { List<Weapon> result = new LinkedList<>(); in.beginArray(); while (in.hasNext()) { switch (in.peek()) { case STRING: result.add(createCommonWeapon(in)); break; case BEGIN_OBJECT: result.add(createUniqueWeapon(in)); break; default: in.skipValue(); break; } } return result; } private Weapon createCommonWeapon(JsonReader in) throws IOException { return new Weapon(in.nextString()); } private Weapon createUniqueWeapon(JsonReader in) throws IOException { UniqueWeapon weapon = new UniqueWeapon(); in.beginObject(); while (in.hasNext()) { switch (in.nextName()) { case "name": String[] tmp = in.nextString().split(" from "); weapon.setName(tmp[0]); if (tmp.length > 1) weapon.setOrigin(tmp[1]); break; case "type": weapon.setType(in.nextString()); break; default: in.skipValue(); break; } } in.endObject(); return weapon; } }
      
      







そして再び


そのため、メソッドはそこで変換を行いますwrite()



彼のコードは:

 public void write(JsonWriter out, List<Weapon> value) throws IOException { out.beginArray(); for (Weapon weapon : value) { if (weapon instanceof UniqueWeapon) { UniqueWeapon uWeapon = (UniqueWeapon) weapon; out.beginObject(); out.name("name") .value(uWeapon.getName() + " from " + uWeapon.getOrigin()); out.name("type") .value(uWeapon.getType()); out.endObject(); } else { out.value(weapon.getType()); } } out.endArray(); }
      
      





メソッドのパラメーターには、JsonWriterクラスのインスタンスと武器のリストがあります。JsonWriter



ストリーミングモードで出力JSONを作成できます。まず、武器データを保存する配列が必要です。

 out.beginArray(); ... out.endArray();
      
      





実際、これらのコマンドは、角括弧を配置する役割を果たします(実際、JSONの配列が指定されているため)。出力で配列を取得したいので、メソッドの最初で開始し、最後で終了します。ここではすべてが非常に簡単です。同様に、メソッド<codebeginObject()</ codeおよび<codeendObject()</ codeを使用してオブジェクトを作成します。

さらに、従来の武器の場合、メソッドを呼び出すことにより、単純にプリミティブ型(文字列)の値を配列に書き込みますvalue()





 out.value(weapon.getType());
      
      





そして、ユニークな武器の場合、オブジェクトを作成し、2つのキーと値のペアを書き込み、交互にname()



メソッドを呼び出しますvalue()





 out.name("name") .value(uWeapon.getName() + " from " + uWeapon.getOrigin()); out.name("type") .value(uWeapon.getType());
      
      





それだけです、武器の配列が記録されます。



そして再び


武器を混合データ型のJSON配列にかなり威勢よく変換しましたか?そして今、それを元に戻す時です。そして、ここで小さな問題が待っています。そのため、メソッドread()



は1つのパラメーターを取ります。

 public List<Weapon> read(JsonReader in) throws IOException {...}
      
      





JsonReaderクラス、Jsonからデータを抽出するほか、ストリーム形式でも使用します。したがって、すべての「ノード」を一貫してソートし、それに応じて処理する必要があります。

記録と同様に、オブジェクトと配列はbeginObject() / endObject()



およびメソッドによって処理されますbeginArray() / endArray()





オブジェクトのプロパティは、メソッドnextName()



、値-メソッドnext{Type}()



(たとえば、nextString()



)でソートします。配列要素もメソッドによって繰り返されnext{Type}()



ます。

ただし、特定の要素のシーケンスを使用した厳密なデータ形式を使用している場合、これはすべて良好です。そして、いつ配列を開くか、いつオブジェクトを開くかなどがわかります。このケースでは、Jsonオブジェクトと文字列を任意の順序で配置できる配列の混合データ型を扱っています。幸いなことにGsonReader



またpeek()



、次のノードのタイプを処理せずに返すメソッドもあります。

したがって、メソッドの一般的なビューは次のread()



ようになります。

 @Override public List<Weapon> read(JsonReader in) throws IOException { List<Weapon> result = new LinkedList<>(); in.beginArray(); while (in.hasNext()) { switch (in.peek()) { case STRING: result.add(createCommonWeapon(in)); break; case BEGIN_OBJECT: result.add(createUniqueWeapon(in)); break; default: in.skipValue(); break; } } in.endArray(); return result; }
      
      





gnomeの兵器庫は、オブジェクト(一意のインスタンスの場合)と文字列(通常のインスタンスの場合)を含む配列で表されることがわかっています。したがって、配列の各要素を処理して、この要素の初期ノードのタイプを確認します。文字列とオブジェクトを処理するために、呼び出すメソッドを作成しました。他のタイプはメソッドによって単にスキップされskipValue()



ます。



従来の武器を作成する方法は非常に簡単です。

 private Weapon createCommonWeapon(JsonReader in) throws IOException { return new Weapon(in.nextString()); }
      
      





メソッドnextString()



使用して武器のタイプを含む行を取得し、それに基づいてオブジェクトを作成します。



ユニークな武器で-もう少し複雑:

 private Weapon createUniqueWeapon(JsonReader in) throws IOException { UniqueWeapon weapon = new UniqueWeapon(); in.beginObject(); while (in.hasNext()) { switch (in.nextName()) { case "name": String[] tmp = in.nextString().split(" from "); weapon.setName(tmp[0]); if (tmp.length > 1) weapon.setOrigin(tmp[1]); break; case "type": weapon.setType(in.nextString()); break; default: in.skipValue(); break; } } in.endObject(); return weapon; }
      
      







オブジェクトに入り、メソッドを使用してそのすべてのプロパティを反復処理しますnextName()



「name」および「type」という名前のプロパティには、処理アルゴリズムがあります-それらに基づいて、従来のユニークな武器のインスタンスを作成します。残りのプロパティ(ある場合)も、スキップされます。



したがって、TypeAdapterを使用したgnomeの兵器のシリアル化解除の準備ができました。

念のため、すべてが正常かどうかを確認します。



検証コード
 DwarvesBand company = BandUtil.createBand(); Gson gson; Type weaponsType = new TypeToken<List<Weapon>>(){}.getType(); gson = new GsonBuilder() .registerTypeAdapter(Dwarf.class, new DwarfSerializerWithTypeAdapter()) .registerTypeAdapter(FacialHair.class, new FacialHairSerializer()) .registerTypeAdapter(DwarvesBand.class, new DwarvesBandSerializer()) .registerTypeAdapter(weaponsType, new WeaponsTypeAdapter()) .setPrettyPrinting() .create(); String json = gson.toJson(company); System.out.println("Serialized:"); System.out.println(json); gson = new GsonBuilder() .registerTypeAdapter(DwarvesBand.class, new DwarvesBandDeserializer()) .registerTypeAdapter(FacialHair.class, new FacialHairDeserializer()) .registerTypeAdapter(Dwarf.class, new DwafDeserializerWithTypeAdapter()) .registerTypeAdapter(weaponsType, new WeaponsTypeAdapter()) .create(); DwarvesBand companyIsBack = gson.fromJson(json, DwarvesBand.class); gson = new GsonBuilder() .setPrettyPrinting() .create(); System.out.println("\n\nDeserialized:"); System.out.println(gson.toJson(companyIsBack));
      
      







結果
 Serialized: { "Orin": { "age": 90, "facialHair": "black beard and mustache", "weapons": [ { "name": "Slasher from Gondolin", "type": "sword" }, { "name": "Oaken Shield from Moria", "type": "shield" }, "dagger" ] }, "Kori": { "age": 60, "facialHair": "red mustache", "weapons": [ "mace", "bow" ] }, "Billy Bob": { "age": 45, "facialHair": "is he really a dwarf?", "weapons": [] } } Deserialized: { "dwarves": [ { "name": "Orin", "facialHair": { "haveBeard": true, "haveMustache": true, "color": "black" }, "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, { "name": "Oaken Shield", "origin": "Moria", "type": "shield" }, { "type": "dagger" } ], "dwarfAge": 90 }, { "name": "Kori", "facialHair": { "haveBeard": false, "haveMustache": true, "color": "red" }, "weapons": [ { "type": "mace" }, { "type": "bow" } ], "dwarfAge": 60 }, { "name": "Billy Bob", "facialHair": { "haveBeard": false, "haveMustache": false, "color": "" }, "weapons": [], "dwarfAge": 45 } ] }
      
      









あとがき



したがって、JavaからJSONへ、そしてその逆への旅は終わりました。これについては、読者の皆さん、私の休暇を取らせてください。あなたが興味を持っていたことを願っています。

役に立つかもしれないいくつかのリンクを思い出させてください:







そして彼らはその後も幸せに暮らしました。

終わり。



All Articles