Kotlin Moshi适配器在库引发JsonDataException时返回null。

huangapple go评论127阅读模式
英文:

Kotlin Moshi adapter return null when library throw JsonDataException

问题

We are using old backend. Sometimes list of objects is returned when contract says it should be text (and other ways).

How/where can we handle JsonDataException Expected a string but was BEGIN_OBJECT so we can remove object with null value for wrong parsed data?

{
...
   "data":{
      "value1":"example",
      "value2":2,
      "value3":"example",
      "value4":"example"
   },
   "other_data":"example",
   "data2":"only string"
}
{
...
   "data":"", //how to ignore this part -> set null?
   "other_data":"example",
   "data2":{ //how to ignore this part?
      "value":2
   }
}

Should this be handled in adapter, if yes is there a need to parse all json manually?

英文:

We are using old backend. Sometimes list of objects is returned when contract says it should be text (and other ways).

How/where can we handle JsonDataException Expected a string but was BEGIN_OBJECT so we can remove object with null value for wrong parsed data?

{
...
   "data":{
      "value1":"example",
      "value2":2,
      "value3":"example",
      "value4":"example"
   },
   "other_data":"example",
   "data2":"only string"
}
{
...
   "data":"", //how to ignore this part -> set null?
   "other_data":"example",
   "data2":{ //how to ignore this part?
      "value":2
   }
}

Should this be handled in adapter, if yes is there a need to parse all json manually?

答案1

得分: 1

是的,你可以为这个目的制作一个 Moshi 适配器。你只需要提前查看,看看它是否是你期望的值,如果不是的话就跳过这个值。

这是一个通用的 Kotlin 适配器示例:

@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
internal annotation class SpecifiedTypeOrNull(val token: JsonReader.Token) {
  class Adapter(
    private val delegate: JsonAdapter<Any?>,
    private val token: JsonReader.Token
  ) : JsonAdapter<Any?>() {
    override fun fromJson(reader: JsonReader): Any? {
      if (reader.peek() != token) {
        reader.skipValue()
        return null
      }
      return delegate.fromJson(reader)
    }

    override fun toJson(writer: JsonWriter, value: Any?) {
      delegate.toJson(writer, value)
    }

    class Factory : JsonAdapter.Factory {
      override fun create(
        type: Type,
        annotations: Set<Annotation>,
        moshi: Moshi
      ): JsonAdapter<*>? {
        if (annotations.isEmpty()) {
          return null
        }
        var token: JsonReader.Token? = null
        var nextAnnotations: MutableSet<Annotation>? = null
        for (annotation in annotations) {
          if (annotation is SpecifiedTypeOrNull) {
            token = annotation.token
            nextAnnotations = annotations.toMutableSet()
            nextAnnotations -= annotation
          }
        }
        if (token == null) {
          return null
        }
        val delegate = moshi.nextAdapter<Any?>(this, type, nextAnnotations!!)
        return Adapter(delegate, token)
      }
    }
  }
}

以下是如何在你的代码中使用它的示例:

@JsonClass(generateAdapter = true)
data class Foo(
  @SpecifiedTypeOrNull(JsonReader.Token.BEGIN_OBJECT) val data: Data?,
  val other_data: String,
  @SpecifiedTypeOrNull(JsonReader.Token.STRING) val data2: String?
) {
  @JsonClass(generateAdapter = true)
  data class Data(val value1: String)
}

val encoded1 = """
  {
     "data":{
        "value1":"example",
        "value2":2,
        "value3":"example",
        "value4":"example"
     },
     "other_data":"example",
     "data2":"only string"
  }
"""

val encoded2 = """
  {
     "data":"",
     "other_data":"example",
     "data2":{
        "value":2
     }
  }
"""

fun main() {
  val moshi = Moshi.Builder()
    .add(SpecifiedTypeOrNull.Adapter.Factory())
    .build()
  val adapter = moshi.adapter(Foo::class.java)
  val decoded1 = adapter.fromJson(encoded1)
  val decoded2 = adapter.fromJson(encoded2)
  println(decoded1)
  println(decoded2)
}
英文:

Yes, you can make a Moshi adapter for this. You'll just need to peek ahead, see if it's the value you expect, and skip the value if it's not.

Here's a generalized adapter in Kotlin.

@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
internal annotation class SpecifiedTypeOrNull(val token: JsonReader.Token) {
  class Adapter(
    private val delegate: JsonAdapter<Any?>,
    private val token: JsonReader.Token
  ) : JsonAdapter<Any?>() {
    override fun fromJson(reader: JsonReader): Any? {
      if (reader.peek() != token) {
        reader.skipValue()
        return null
      }
      return delegate.fromJson(reader)
    }

    override fun toJson(writer: JsonWriter, value: Any?) {
      delegate.toJson(writer, value)
    }

    class Factory : JsonAdapter.Factory {
      override fun create(
        type: Type,
        annotations: Set<Annotation>,
        moshi: Moshi
      ): JsonAdapter<*>? {
        if (annotations.isEmpty()) {
          return null
        }
        var token: JsonReader.Token? = null
        var nextAnnotations: MutableSet<Annotation>? = null
        for (annotation in annotations) {
          if (annotation is SpecifiedTypeOrNull) {
            token = annotation.token
            nextAnnotations = annotations.toMutableSet()
            nextAnnotations -= annotation
          }
        }
        if (token == null) {
          return null
        }
        val delegate = moshi.nextAdapter<Any?>(this, type, nextAnnotations!!)
        return Adapter(delegate, token)
      }
    }
  }
}

Here's how you can use it for your above code.

@JsonClass(generateAdapter = true)
data class Foo(
  @SpecifiedTypeOrNull(JsonReader.Token.BEGIN_OBJECT) val data: Data?,
  val other_data: String,
  @SpecifiedTypeOrNull(JsonReader.Token.STRING) val data2: String?
) {
  @JsonClass(generateAdapter = true)
  data class Data(val value1: String)
}

val encoded1 = """
  {
     "data":{
        "value1":"example",
        "value2":2,
        "value3":"example",
        "value4":"example"
     },
     "other_data":"example",
     "data2":"only string"
  }
"""

val encoded2 = """
  {
     "data":"",
     "other_data":"example",
     "data2":{
        "value":2
     }
  }
"""

fun main() {
  val moshi = Moshi.Builder()
    .add(SpecifiedTypeOrNull.Adapter.Factory())
    .build()
  val adapter = moshi.adapter(Foo::class.java)
  val decoded1 = adapter.fromJson(encoded1)
  val decoded2 = adapter.fromJson(encoded2)
  println(decoded1)
  println(decoded2)
}

huangapple
  • 本文由 发表于 2023年3月7日 22:03:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/75662991.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定