← Назад к вопросам
Как определить пользовательский тип данных в JSON?
1.0 Junior🔥 131 комментариев
#REST API и микросервисы#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Определение пользовательских типов данных в JSON
JSON сам по себе поддерживает только базовые типы: string, number, boolean, null, array, object. Для работы с пользовательскими типами в Java используются различные подходы.
Проблема
JSON не имеет встроенного способа сохранить информацию о типе. Когда мы десериализуем JSON в Java объект, парсер не знает, какой класс использовать.
Решение 1: Jackson с @JsonTypeInfo
// Определение иерархии с type информацией
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = Circle.class, name = "circle"),
@JsonSubTypes.Type(value = Square.class, name = "square")
})
public abstract class Shape {
public abstract void draw();
}
public class Circle extends Shape {
public double radius;
@Override
public void draw() { System.out.println("Drawing circle"); }
}
public class Square extends Shape {
public double side;
@Override
public void draw() { System.out.println("Drawing square"); }
}
// Использование
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(new Circle());
// Результат: {"type":"circle", "radius": 5.0}
Shape shape = mapper.readValue(json, Shape.class);
Решение 2: Кастомный Deserializer
public class ShapeDeserializer extends StdDeserializer<Shape> {
private static final Map<String, Class<? extends Shape>> typeMap = new HashMap<>();
static {
typeMap.put("circle", Circle.class);
typeMap.put("square", Square.class);
}
public ShapeDeserializer() {
super(Shape.class);
}
@Override
public Shape deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
JsonNode node = p.getCodec().readTree(p);
String type = node.get("type").asText();
Class<? extends Shape> shapeClass = typeMap.get(type);
if (shapeClass == null) {
throw new JsonMappingException(p, "Unknown shape type: " + type);
}
return p.getCodec().treeToValue(node, shapeClass);
}
}
// Регистрация deserializer
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Shape.class, new ShapeDeserializer());
mapper.registerModule(module);
Решение 3: Polymorphic deserialization с @JsonCreator
public abstract class Animal {
public String name;
@JsonCreator
public static Animal create(@JsonProperty("type") String type,
@JsonAnySetter Map<String, Object> properties) {
switch (type) {
case "dog":
Dog dog = new Dog();
dog.name = (String) properties.get("name");
return dog;
case "cat":
Cat cat = new Cat();
cat.name = (String) properties.get("name");
return cat;
default:
throw new IllegalArgumentException("Unknown type: " + type);
}
}
}
public class Dog extends Animal {}
public class Cat extends Animal {}
Решение 4: JSON с дополнительными метаданными
// Обёртка для сохранения типа
public class TypedValue<T> {
public String type;
public T data;
public TypedValue(T data) {
this.data = data;
this.type = data.getClass().getSimpleName();
}
}
// Использование
Circle circle = new Circle();
TypedValue<Circle> typed = new TypedValue<>(circle);
String json = mapper.writeValueAsString(typed);
// Результат: {"type":"Circle", "data": {...}}
Решение 5: Использование discriminator (OpenAPI/JSON Schema)
@Data
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "petType",
defaultImpl = Dog.class,
visible = true
)
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "dog"),
@JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public abstract class Pet {
public String petType;
public String name;
}
@Data
public class Dog extends Pet {
public String breed;
}
@Data
public class Cat extends Pet {
public int lives;
}
GSON альтернатива
public class GsonTypeAdapter extends TypeAdapter<Shape> {
@Override
public Shape read(JsonReader in) throws IOException {
JsonElement element = JsonParser.parseReader(in);
String type = element.getAsJsonObject().get("type").getAsString();
switch (type) {
case "circle":
return gson.fromJson(element, Circle.class);
case "square":
return gson.fromJson(element, Square.class);
default:
throw new IllegalArgumentException("Unknown type");
}
}
@Override
public void write(JsonWriter out, Shape shape) throws IOException {
out.jsonValue(gson.toJson(shape));
}
}
// Регистрация
Gson gson = new GsonBuilder()
.registerTypeAdapter(Shape.class, new GsonTypeAdapter())
.create();
Best Practices
- Используй @JsonTypeInfo для простых случаев
- Кастомные Deserializers для сложной логики
- Не полагайся на порядок полей в JSON
- Всегда проверяй тип перед использованием
- Версионируй API при изменении типов
Сравнение подходов
| Подход | Простота | Гибкость | Использование |
|---|---|---|---|
| @JsonTypeInfo | ✓✓✓ | ✓✓ | Рекомендуется |
| Custom Deserializer | ✓ | ✓✓✓ | Сложные случаи |
| @JsonCreator | ✓✓ | ✓✓ | Альтернатива |
| Type wrapper | ✓✓✓ | ✓ | Простые типы |
Для большинства случаев Jackson с @JsonTypeInfo — это лучший выбор.