Десериализовать JSON в запись общего типа

У меня есть служба, возвращающая JSON со «стабильным» ответом, но содержащая поле «проекции», которое может варьироваться. Пример:

{
  "name": "test",
  "projection": {
    "id": 1,
    "description": "Hey there",
    "blurb": "This is a test"
  }
}

Я хочу выполнить десериализацию в записи и предоставить механизм, позволяющий определить собственную запись для возвращенной проекции. Вот что я придумал: запись Response с общим членом, реализующим интерфейс Projection.

package com.example.json

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import java.io.IOException;

public class Example {
  private static final ObjectMapper MAPPER =
      new ObjectMapper()
          .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
          .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, true)
          .registerModule(new Jdk8Module());

  private static final String SAMPLE_JSON =
      "{\"name\":\"test\", \"projection\": {\"id\": 1, \"description\": \"Hey there\", \"blurb\": \"This is a test\"}}";

  record Response<T extends Projection>(String name, T projection) {}

  interface Projection {
    int id();

    static <T extends Projection> Response<T> parse(byte[] bytes) {
      try {
        TypeReference<Response<T>> typeReference = new TypeReference<>() {};
        return MAPPER.readValue(bytes, typeReference);  // Explodes here
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }
  }

  record Projection1(int id, String description, String blurb) implements Projection {}

  public static void main(String[] args) {
    Response<Projection1> response = Projection.<Projection1>parse(SAMPLE_JSON.getBytes());

    System.out.println(response);
  }
}

Это не удается с ошибкой:

Exception in thread "main" java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.json.Example$Projection` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 31] (through reference chain: com.example.json.Example$Response["projection"])
    at com.example.json.Example$Projection.parse(Example.java:29)
    at com.example.json.Example.main(Example.java:37)

Я предвижу довольно много вариантов прогнозов. Есть ли способ сохранить логику синтаксического анализа в интерфейсе/одном месте и избежать необходимости, например. реализовать метод синтаксического анализа для каждой конкретной реализации Projection, который будет иметь доступ к конкретным типам?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
328
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это заработало, добавив TypeReference в подпись Projection::parse.

  interface Projection {
    int id();

    static <T extends Projection> Response<T> parse(byte[] bytes, TypeReference<Response<T>> typeReference) {
      try {
        return MAPPER.readValue(bytes, typeReference);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }

И затем передайте его при вызове:

  public static void main(String[] args) {
    Response<Projection1> response = Projection.parse(SAMPLE_JSON.getBytes(), new TypeReference<>() {});
    System.out.println(response);
  }

В идеале я бы не хотел пропускать TypeReference, но это работает.

Объект new TypeReference<>() {} предлагает проверку как во время компиляции, так и во время выполнения, поэтому вам нужно будет передать его. Раньше я пытался заставить вызывающий метод определить тип, но это всегда заканчивалось ошибкой во время выполнения.

Mr. Polywhirl 22.03.2024 18:19

Другие вопросы по теме