Фабрика Moshi игнорирует нулевые значения и использует значение kotlin по умолчанию при десериализации из строки json - PullRequest
0 голосов
/ 24 февраля 2020

Основываясь на этом вопросе в Моши https://github.com/square/moshi/issues/843, состоялась хорошая дискуссия о том, почему и как Моши относится к нулям так, как он это делает. Хотя это имеет большой смысл для меня, я, к сожалению, работаю с API, который периодически возвращает нули, и лучшая обратная связь, которую я получил, - то, что мы должны просто игнорировать их. В указанной выше проблеме один из авторов библиотеки упоминает эту фабрику, которая может помочь.

@Retention(RUNTIME)
@Target(CLASS)
annotation class DefaultIfNull

class DefaultIfNullFactory : JsonAdapter.Factory {
  override fun create(type: Type, annotations: MutableSet<out Annotation>,
      moshi: Moshi): JsonAdapter<*>? {
    if (!Types.getRawType(type).isAnnotationPresent(
            DefaultIfNull::class.java)) {
      return null
    }

    val delegate = moshi.nextAdapter<Any>(this, type, annotations)

    return object : JsonAdapter<Any>() {
      override fun fromJson(reader: JsonReader): Any? {
        @Suppress("UNCHECKED_CAST")
        val blob = reader.readJsonValue() as Map<String, Any?>
        val noNulls = blob.filterValues { it != null }
        return delegate.fromJsonValue(noNulls)
      }

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

class NullSkipperTest {

  @DefaultIfNull
  data class ClassWithDefaults(val foo: String, val bar: String? = "defaultBar")

  @Test
  fun skipNulls() {
    //language=JSON
    val json = """{"foo": "fooValue", "bar": null}"""

    val adapter = Moshi.Builder()
        .add(DefaultIfNullFactory())
        .add(KotlinJsonAdapterFactory())
        .build()
        .adapter(ClassWithDefaults::class.java)

    val instance = adapter.fromJson(json)!!
    check(instance.bar == "defaultBar")
  }
}

Это прекрасно работает, но не является "оптимальным" для моего варианта использования из-за двух отдельных проблем: 1. Это требует аннотирования каждого класса. У меня есть около 200 классов, которые я должен аннотировать, и поэтому я ищу фабрику, которая бы просто делала это вместо того, чтобы включить ее. Если что, возможно, я бы хотел отказаться, но это не имеет значения, верно Теперь 2. Из теста, который также был опубликован, не ясно, нужно ли объявлять все мои значения как обнуляемые. Я также хотел бы не отмечать каждое поле как обнуляемый тип, но тест, кажется, проходит как val foo: String, val bar: String = "defaultBar" вместо val foo: String, val bar: String? = "defaultBar"

Чтобы заставить это работать на себя, я преобразовал фрагмент к этому. И это также, кажется, решает мои две "проблемы", которые я заявляю выше.

class DefaultIfNullFactory : JsonAdapter.Factory {
    override fun create(type: Type, annotations: MutableSet<out Annotation>,
                        moshi: Moshi): JsonAdapter<*>? {
        val delegate = moshi.nextAdapter<Any>(this, type, annotations)
        return object : JsonAdapter<Any>() {
            override fun fromJson(reader: JsonReader): Any? {
                    val blob1 = reader.readJsonValue()
                try {
                    val blob = blob1 as Map<String, Any?>
                    val noNulls = blob.filterValues { it != null }
                    return delegate.fromJsonValue(noNulls)
                } catch (e: Exception) {
                    return delegate.fromJsonValue(blob1)
                }
            }
            override fun toJson(writer: JsonWriter, value: Any?) {
                return delegate.toJson(writer, value)
            }
        }
    }
}

То, что я не знал о фабрике, это то, что она перемещается по каждому полю в классе, а также в классе, поэтому, когда у меня изначально было это, я думал, что это будет идеально, но он действительно вызывается для каждого поля внутри каждого класса, не проходит приведение, а затем просто возвращает значение.

Я нахожусь в точке, где я доволен этим, и я не воспринимаю ничего хиты производительности. Кто-нибудь с большим опытом работы с моши скажет, что это ужасно, или это лучшее, что я могу получить для своих ограничений?

РЕДАКТИРОВАТЬ:

Хотя я и не У меня нет ответа на вопрос, является ли это хорошим подходом. Я могу подтвердить, что это породило две скрытые проблемы, и я могу НЕ рекомендовать вам go этот маршрут.

  1. Числа, которые являются длинными в json, такие как 1583674200000, не будут работать, если вы адаптировали их для преобразования в тип java String. Можно подумать, что вы получите «1583674200000», но на самом деле вы получите «1.5836742E12»

  2. Если у вас есть строковое число, оно будет преобразовано в двойное число. Таким образом, у вас может быть «100», но он будет конвертирован в 100.0.

Это происходит потому, что числа в moshi являются двойными, а причина root - readJsonValue () как Карта, так как она сначала должна отобразить все значения в значения по умолчанию, которые моши может понять, чтобы затем отфильтровать нули. Это причина моих проблем.

TLDR Не используйте эту фабрику.

РЕДАКТИРОВАТЬ 2: Я был уведомлен о том, что мои правки здесь неправильные. Я не совсем уверен, что это так, но мы обсуждаем это здесь https://github.com/square/moshi/issues/843

...