gson がデータクラスに逆シリアル化するときの落とし穴

序文

Android 開発では、gson は json を処理するために非常に一般的に使用されるサードパーティ ライブラリであり、Google によって保守されており、少なくとも Java で開発されている場合には常に比較的安定しています。

ただし、Kotlin のデータ クラスに対する gson のサポートは完全ではなく、落とし穴がいくつかあります。


gson をデータクラスに逆シリアル化する通常の状況

kotlin では、データ クラスとして機能するデータ クラスを使用します。次に例を示します。

data class User(
    val name: String,
    val age: Int,
)

次のように、gson を使用して json をオブジェクトに解析することも非常に簡単です。

fun main() {
    
    
    val jsonStr = """{"name":"喻志强","age":0}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${
      
      user}")
}

ここに画像の説明を挿入

次に、User オブジェクトを取得します。データは問題ありません。以前に gson の使用に関するいくつかのヒントとパッケージも書きました。興味がある場合は、以下を参照してください:
Gson で使用されるいくつかのヒント
上記の状況は、通常の json データに基づいています。ただし、JSON データが正常でない場合、ピットが生成されるので、見てみましょう


属性値を null にすることはできません。逆シリアル化後に null 値が表示されます。

上記の例でも、データ クラスは次のとおりです。

data class User(
    val name: String,
    val age: Int,
)

jsonデータ内の名前がなくなった場合は以下のように

fun main() {
    
    
    val jsonStr = """{"age":0}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${user}")
}

解析されたオブジェクトは以下の通り
ここに画像の説明を挿入
一見何も問題がないように見えますが、nameフィールドが存在せず、nameの値がnullになっているだけではないでしょうか?

コードを書くときに注意しない場合は、名前操作と同じようなコードを書いてください。

fun main() {
    
    
    val jsonStr = """{name:null,age:18}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${
      
      user}")
    val substring = user.name.substring(0, 1)
    /*获取name的第一个字符*/
    println("substring = ${
      
      substring}")
}

実行時にエラーが報告されませんか
ここに画像の説明を挿入
? 名前は null ですが、よく考えると、これは間違っています。これは明らかに kotlin の構文と矛盾します。データ クラスの属性の値が null になれる場合は、それを使用する必要があることは誰もが知っています。識別するには、次のようにします

ここに画像の説明を挿入
このようにして、コードを記述するときに名前を操作すると、IDE は疑問符を追加するように求めます。これにより、null ポインター例外の発生を効果的に回避できます。? 記号を追加して実行すると、確かにエラーは発生しません
ここに画像の説明を挿入
。報告される。
ここに画像の説明を挿入
ただし、データ クラスの名前に疑問符が追加されていない場合は、null 値も表示されます。これが最初の落とし穴であり、期待と一致しません。
その理由は、gson はオブジェクトの構築に Java リフレクションを使用するためです。つまり、gson はデータ クラスを知らないため、kotlin の null 安全機能を満たさず、奇妙な null ポインタ例外が発生しやすくなります。

この問題を解決する方法は非常に簡単で、「?」を追加するだけです。以上です。個人的には、null セキュリティの疑問符を使用するのは好きではありません。書くのは少し嫌です。どこにでも疑問符が追加されます。ただし、null ポインター例外を効果的に回避できますが、ビジネス シナリオによっては、一部のフィールドが値が必要であり、null にすることはできません。null の場合は、データが異常であることを意味します。逆シリアル化中に直接例外をスローしてください。
gson の落とし穴は、デシリアライズ中にデータが異常であることを伝えられないことであり、このフィールドの値を使用した場合にのみ NPE が表示され、この時点では手遅れになります...


何?データクラスのデフォルト値は有効になりませんか?

一部のシナリオでは、データにいくつかのデフォルト値が必要な場合や、上記の null 値の問題を解決するために、名前にデフォルト値を与え、json に name フィールドがない場合はデフォルト値を使用できることを期待します。 name の値は次のようになります。

data class User(
    val name: String="喻志强",
    val age: Int,
)

その後、再度実行します

fun main() {
    
    
    val jsonStr = """{age:28}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${
      
      user}")
    val substring = user.name.substring(0, 1)
    /*获取name的第一个字符*/
    println("substring = ${
      
      substring}")
}

まだエラー
ここに画像の説明を挿入

デフォルト値が有効になっておらず、null のままであることがわかります。ブレークポイントに到達すると、gson が User のパラメーターのない構造を探したときにその値が見つからなかったことが原因であることがわかります。オブジェクトを直接作成すると、構築はまったく行われず、構築は行われませんUnsafeAllocator.create()。デフォルト値は当然有効になりません。
ここに画像の説明を挿入

この問題を解決するのも非常に簡単で、各属性に初期値を割り当てると、パラメータのないコンストラクタが生成されるため、この問題は発生しません。

data class User(
    val name: String="喻志强",
    val age: Int=0,
)

再実行

fun main() {
    
    
    val jsonStr = """{age:18}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${
      
      user}")
    val substring = user.name.substring(0, 1)
    println("substring = ${
      
      substring}")
}

ここに画像の説明を挿入

これは私自身の日々の開発のやり方でもあり、各属性にデフォルト値を与えることに慣れており、コードを書くときに扱いやすくなっています。
しかし、デフォルト値を与えるのは確実なのでしょうか? もちろんそうではありません。
次のコードをもう一度見てみましょう。

fun main() {
    
    
    val jsonStr = """{name:null,age:18}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${
      
      user}")
    val substring = user.name.substring(0, 1)
    println("substring = ${
      
      substring}")
}

データクラスのすべての属性にデフォルト値を与えましたが、json 内の属性の値が明示的に null である場合、null 値は依然として元のデフォルト値をカバーすることになり、再び最初の落とし穴にはまってしまいます。コードを記述するときに null にする必要があり、コードを記述するときに例外プロンプトは表示されませんが、結果として実行時に NPE が表示されます。

返されたデータが標準化されていないこの種の状況に対処する良い方法は実際にはありません。これに対処する唯一の良い方法は、バックエンドにデータを強制的に変更させることです...

ここに画像の説明を挿入
しかし、より良い解決策も見つけました。自分でバックエンドを書いて基礎を学ぶだけで十分です。助けを求める必要はありません。それが私です...

ここに画像の説明を挿入

さて、もうインストールするのはやめて、gson をデータ クラスと組み合わせるときに発生する可能性のある 2 つの主な問題をまとめてみましょう。

  • 属性が宣言されているときは値を null にすることはできませんが、逆シリアル化後の結果は null になりますが、これは期待どおりではありません。
  • デフォルト値は有効にならず、null で上書きされる可能性があります。

実際、上記の問題にはいくつかの解決策がありますので、自分で調べてください。しかし、問題が根本的な原因から解決されていないため、私はあまり好きではありません。

根本原因から解決したい場合は非常に簡単です。gson の代わりに moshi や jackson を使用できます。これらはいずれも kotlin の別の処理を行います。具体的な使い方については公式ドキュメントもあります。まずは github のアドレスです。

moshi:https://github.com/square/moshi

ジャクソン:ジャクソンモジュール-kotlin

次回のブログではmoshiの基本的な使い方と実戦について書いていきますので、興味のある方はぜひご覧ください。

kotlin に優しい最新の JSON ライブラリである moshi の基本的な使用法と実践方法


この記事が役立つと思われる場合は、高評価をお願いします。より多くの開発者を助けることができます。記事に間違いがある場合は、修正してください。転載する場合は、Yu Zhiqiang のブログからの転載であることを明記してください。ありがとう_

おすすめ

転載: blog.csdn.net/yuzhiqiang_1993/article/details/123953523