У меня есть служба, возвращающая 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, который будет иметь доступ к конкретным типам?




Это заработало, добавив 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<>() {}предлагает проверку как во время компиляции, так и во время выполнения, поэтому вам нужно будет передать его. Раньше я пытался заставить вызывающий метод определить тип, но это всегда заканчивалось ошибкой во время выполнения.