Различить guish ноль от отсутствующих свойств в Джексоне, используя Kotlin классы данных - PullRequest
2 голосов
/ 19 февраля 2020

Я хочу использовать Джексона для десериализации и последующей сериализации jsons с использованием классов данных Kotlin. Важно, чтобы я поддерживал различие между явно нулевым свойством и свойством, которое было опущено в исходном json.

У меня есть модель большой области (50+ классов), построенная почти полностью из Kotlin классы данных. Классы данных Kotlin предоставляют множество полезных функций, которые мне нужно использовать в других местах моей программы, и по этой причине я бы хотел сохранить их вместо преобразования своих моделей.

В настоящее время у меня есть этот код работает, но только для классов Java или с использованием свойств Kotlin, объявленных в теле класса Kotlin, и не работает для свойств, объявленных в конструкторе. Для работы служебных функций класса данных Kotlin все свойства должны быть объявлены в конструкторе.

Вот мой код установки средства отображения объектов:

val objectMapper = ObjectMapper()

objectMapper.registerModule(KotlinModule())
objectMapper.registerModule(Jdk8Module())

objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS)
objectMapper.configOverride(Optional::class.java).includeAsProperty =
    JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, null)

objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true)

objectMapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true)

objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true)
objectMapper.nodeFactory = JsonNodeFactory.withExactBigDecimals(true)

Вот мои тестовые классы:

TestClass1. java

public class TestClass1 {
    public TestClass1() {}
    public TestClass1(int intVal, Optional<Double> optDblVal) {
        this.intVal = intVal;
        this.optDblVal = optDblVal;
    }
    public Integer intVal;
    public Optional<Double> optDblVal;
}

TestClasses.kt

data class TestClass2(val intVal: Int?, val optDblVal: Optional<Double>?)

class TestClass3(val intVal: Int?, val optDblVal: Optional<Double>?)

class TestClass4 {
    val intVal: Int? = null
    val optDblVal: Optional<Double>? = null
}

и вот мои тесты:

JsonReserializationTests.kt

@Test
fun `Test 1 - Explicit null Double reserialized as explicit null`() {
    val inputJson = """
        {
          "intVal" : 7,
          "optDblVal" : null
        }
        """.trimIndent()

    val intermediateObject = handler.objectMapper.readValue(inputJson, TestClassN::class.java)
    val actualJson = handler.objectMapper
        .writerWithDefaultPrettyPrinter()
        .writeValueAsString(intermediateObject)
        .replace("\r", "")

    assertEquals(inputJson, actualJson)
}

@Test
fun `Test 2 - Missing Double not reserialized`() {
    val inputJson = """
        {
          "intVal" : 7
        }
        """.trimIndent()

    val intermediateObject = handler.objectMapper.readValue(inputJson, TestClassN::class.java)
    val actualJson = handler.objectMapper
        .writerWithDefaultPrettyPrinter()
        .writeValueAsString(intermediateObject)
        .replace("\r", "")

    assertEquals(inputJson, actualJson)
}

Результаты теста для каждого класса

Test Results

1 Ответ

2 голосов
/ 25 февраля 2020

Давайте поговорим о TestClass2.

Если вы конвертируете код Kotlin в код Java, вы можете найти причину.

Intellij предлагает инструмент конвертирования для Kotlin , Вы можете найти его в меню Tools -> Kotlin -> Show Kotlin Bytecode.

Вот код Java из кода TestClass2 Kotlin.

public final class TestClass2 {
   @Nullable
   private final Integer intVal;
   @Nullable
   private final Optional optDblVal;

   @Nullable
   public final Integer getIntVal() {
      return this.intVal;
   }

   @Nullable
   public final Optional getOptDblVal() {
      return this.optDblVal;
   }

   public TestClass2(@Nullable Integer intVal, @Nullable Optional optDblVal) {
      this.intVal = intVal;
      this.optDblVal = optDblVal;
   }

   @Nullable
   public final Integer component1() {
      return this.intVal;
   }

   @Nullable
   public final Optional component2() {
      return this.optDblVal;
   }

   @NotNull
   public final TestClass2 copy(@Nullable Integer intVal, @Nullable Optional optDblVal) {
      return new TestClass2(intVal, optDblVal);
   }

   // $FF: synthetic method
   public static TestClass2 copy$default(TestClass2 var0, Integer var1, Optional var2, int var3, Object var4) {
      if ((var3 & 1) != 0) {
         var1 = var0.intVal;
      }

      if ((var3 & 2) != 0) {
         var2 = var0.optDblVal;
      }

      return var0.copy(var1, var2);
   }

   @NotNull
   public String toString() {
      return "TestClass2(intVal=" + this.intVal + ", optDblVal=" + this.optDblVal + ")";
   }

   public int hashCode() {
      Integer var10000 = this.intVal;
      int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
      Optional var10001 = this.optDblVal;
      return var1 + (var10001 != null ? var10001.hashCode() : 0);
   }

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof TestClass2) {
            TestClass2 var2 = (TestClass2)var1;
            if (Intrinsics.areEqual(this.intVal, var2.intVal) && Intrinsics.areEqual(this.optDblVal, var2.optDblVal)) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }

Исходный код слишком длинный, поэтому здесь только конструктор.

public TestClass2(@Nullable Integer intVal, @Nullable Optional optDblVal) {
      this.intVal = intVal;
      this.optDblVal = optDblVal;
}

Поскольку библиотека Jackson не может создать экземпляр без параметров, поскольку нет конструктора без параметров, она попытается создать новый экземпляр с некоторыми параметрами. Для тестового примера 2 JSON имеет только один параметр, так что он будет искать однопараметрический конструктор, но нет, чтобы он вызывал исключение. По этой же причине передается тестовый пример 1.

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

data class TestClass2(val intVal: Int? = null, val optDblVal: Optional<Double>? = null)

Тогда, если вы видите в коде Java, класс будет иметь конструктор без параметров.

   public TestClass2(@Nullable Integer intVal, @Nullable Optional optDblVal) {
      this.intVal = intVal;
      this.optDblVal = optDblVal;
   }

   // $FF: synthetic method
   public TestClass2(Integer var1, Optional var2, int var3, DefaultConstructorMarker var4) 
   {
      if ((var3 & 1) != 0) {
         var1 = (Integer)null;
      }

      if ((var3 & 2) != 0) {
         var2 = (Optional)null;
      }

      this(var1, var2);
   }

   public TestClass2() {
      this((Integer)null, (Optional)null, 3, (DefaultConstructorMarker)null);
   }

Итак, если вы все еще хотите использовать Kotlin класс данных, вы должны дать значения по умолчанию для всех переменных.

...