ExistingPropertyTest.java
package com.fasterxml.jackson.databind.jsontype;
import java.util.*;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class ExistingPropertyTest extends DatabindTestUtil
{
/**
* Polymorphic base class - existing property as simple property on subclasses
*/
@JsonTypeInfo(use = Id.NAME, include = As.EXISTING_PROPERTY, property = "type",
visible=true)
@JsonSubTypes({
@Type(value = Apple.class, name = "apple") ,
@Type(value = Orange.class, name = "orange")
})
static abstract class Fruit {
public String name;
protected Fruit(String n) { name = n; }
}
@JsonTypeName("apple")
@JsonPropertyOrder({ "name", "seedCount", "type" })
static class Apple extends Fruit
{
public int seedCount;
public String type;
private Apple() { super(null); }
public Apple(String name, int b) {
super(name);
seedCount = b;
type = "apple";
}
}
@JsonTypeName("orange")
@JsonPropertyOrder({ "name", "color", "type" })
static class Orange extends Fruit
{
public String color;
public String type;
private Orange() { super(null); }
public Orange(String name, String c) {
super(name);
color = c;
type = "orange";
}
}
static class FruitWrapper {
public Fruit fruit;
public FruitWrapper() {}
public FruitWrapper(Fruit f) { fruit = f; }
}
/**
* Polymorphic base class - existing property forced by abstract method
*/
@JsonTypeInfo(use = Id.NAME, include = As.EXISTING_PROPERTY, property = "type")
@JsonSubTypes({
@Type(value = Dog.class, name = "doggie") ,
@Type(value = Cat.class, name = "kitty")
})
static abstract class Animal {
public String name;
protected Animal(String n) { name = n; }
public abstract String getType();
}
@JsonTypeName("doggie")
static class Dog extends Animal
{
public int boneCount;
private Dog() { super(null); }
public Dog(String name, int b) {
super(name);
boneCount = b;
}
@Override
public String getType() {
return "doggie";
}
}
@JsonTypeName("kitty")
static class Cat extends Animal
{
public String furColor;
private Cat() { super(null); }
public Cat(String name, String c) {
super(name);
furColor = c;
}
@Override
public String getType() {
return "kitty";
}
}
static class AnimalWrapper {
public Animal animal;
public AnimalWrapper() {}
public AnimalWrapper(Animal a) { animal = a; }
}
/**
* Polymorphic base class - existing property NOT forced by abstract method on base class
*/
@JsonTypeInfo(use = Id.NAME, include = As.EXISTING_PROPERTY, property = "type")
@JsonSubTypes({
@Type(value = Accord.class, name = "accord") ,
@Type(value = Camry.class, name = "camry")
})
static abstract class Car {
public String name;
protected Car(String n) { name = n; }
}
@JsonTypeName("accord")
static class Accord extends Car
{
public int speakerCount;
private Accord() { super(null); }
public Accord(String name, int b) {
super(name);
speakerCount = b;
}
public String getType() {
return "accord";
}
}
@JsonTypeName("camry")
static class Camry extends Car
{
public String exteriorColor;
private Camry() { super(null); }
public Camry(String name, String c) {
super(name);
exteriorColor = c;
}
public String getType() {
return "camry";
}
}
static class CarWrapper {
public Car car;
public CarWrapper() {}
public CarWrapper(Car c) { car = c; }
}
// for [databind#1635]
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
// IMPORTANT! Must be defined as `visible`
visible=true,
property = "type",
defaultImpl=Bean1635Default.class)
@JsonSubTypes({ @JsonSubTypes.Type(Bean1635A.class) })
static class Bean1635 {
public ABC type;
}
@JsonTypeName("A")
static class Bean1635A extends Bean1635 {
public int value;
}
static class Bean1635Default extends Bean1635 { }
// [databind#2785]
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "packetType")
public interface Base2785 {
}
@JsonTypeName("myType")
static class Impl2785 implements Base2785 {
}
// [databind#3251]: Double vs BigDecimal
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type_alias"
)
static class GenericWrapperWithNew3251<T> {
private final T value;
@JsonCreator
public GenericWrapperWithNew3251(@JsonProperty("value") T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "fieldType",
visible = true,
defaultImpl = GenericWrapperWithExisting3251.class
)
static class GenericWrapperWithExisting3251<T> {
public String fieldType;
private final T value;
@JsonCreator
public GenericWrapperWithExisting3251(@JsonProperty("value") T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
// [databind#3271]
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY,
visible = true, property = "type", defaultImpl = DefaultShape3271.class)
@JsonSubTypes({@JsonSubTypes.Type(value = Square3271.class, name = "square")})
static abstract class Shape3271 {
public String type;
public String getType() { return this.type; }
public void setType(String type) { this.type = type; }
}
static class Square3271 extends Shape3271 {}
static class DefaultShape3271 extends Shape3271 {}
/*
/**********************************************************************
/* Mock data
/**********************************************************************
*/
private static final Orange mandarin = new Orange("Mandarin Orange", "orange");
private static final String mandarinJson = "{\"name\":\"Mandarin Orange\",\"color\":\"orange\",\"type\":\"orange\"}";
private static final Apple pinguo = new Apple("Apple-A-Day", 16);
private static final String pinguoJson = "{\"name\":\"Apple-A-Day\",\"seedCount\":16,\"type\":\"apple\"}";
private static final FruitWrapper pinguoWrapper = new FruitWrapper(pinguo);
private static final String pinguoWrapperJson = "{\"fruit\":" + pinguoJson + "}";
private static final List<Fruit> fruitList = Arrays.asList(pinguo, mandarin);
private static final String fruitListJson = "[" + pinguoJson + "," + mandarinJson + "]";
private static final Cat beelzebub = new Cat("Beelzebub", "tabby");
private static final String beelzebubJson = "{\"name\":\"Beelzebub\",\"furColor\":\"tabby\",\"type\":\"kitty\"}";
private static final Dog rover = new Dog("Rover", 42);
private static final String roverJson = "{\"name\":\"Rover\",\"boneCount\":42,\"type\":\"doggie\"}";
private static final AnimalWrapper beelzebubWrapper = new AnimalWrapper(beelzebub);
private static final String beelzebubWrapperJson = "{\"animal\":" + beelzebubJson + "}";
private static final List<Animal> animalList = Arrays.asList(beelzebub, rover);
private static final String animalListJson = "[" + beelzebubJson + "," + roverJson + "]";
private static final Camry camry = new Camry("Sweet Ride", "candy-apple-red");
private static final String camryJson = "{\"name\":\"Sweet Ride\",\"exteriorColor\":\"candy-apple-red\",\"type\":\"camry\"}";
private static final Accord accord = new Accord("Road Rage", 6);
private static final String accordJson = "{\"name\":\"Road Rage\",\"speakerCount\":6,\"type\":\"accord\"}";
private static final CarWrapper camryWrapper = new CarWrapper(camry);
private static final String camryWrapperJson = "{\"car\":" + camryJson + "}";
private static final List<Car> carList = Arrays.asList(camry, accord);
private static final String carListJson = "[" + camryJson + "," + accordJson + "]";
/*
/**********************************************************************
/* Test methods
/**********************************************************************
*/
private final ObjectMapper MAPPER = newJsonMapper();
/**
* Fruits - serialization tests for simple property on sub-classes
*/
@Test
public void testExistingPropertySerializationFruits() throws Exception
{
Map<String,Object> result = writeAndMap(MAPPER, pinguo);
assertEquals(3, result.size());
assertEquals(pinguo.name, result.get("name"));
assertEquals(pinguo.seedCount, result.get("seedCount"));
assertEquals(pinguo.type, result.get("type"));
result = writeAndMap(MAPPER, mandarin);
assertEquals(3, result.size());
assertEquals(mandarin.name, result.get("name"));
assertEquals(mandarin.color, result.get("color"));
assertEquals(mandarin.type, result.get("type"));
String pinguoSerialized = MAPPER.writeValueAsString(pinguo);
assertEquals(pinguoSerialized, pinguoJson);
String mandarinSerialized = MAPPER.writeValueAsString(mandarin);
assertEquals(mandarinSerialized, mandarinJson);
String fruitWrapperSerialized = MAPPER.writeValueAsString(pinguoWrapper);
assertEquals(fruitWrapperSerialized, pinguoWrapperJson);
String fruitListSerialized = MAPPER.writeValueAsString(fruitList);
assertEquals(fruitListSerialized, fruitListJson);
}
/**
* Fruits - deserialization tests for simple property on sub-classes
*/
@Test
public void testSimpleClassAsExistingPropertyDeserializationFruits() throws Exception
{
Fruit pinguoDeserialized = MAPPER.readValue(pinguoJson, Fruit.class);
assertTrue(pinguoDeserialized instanceof Apple);
assertSame(pinguoDeserialized.getClass(), Apple.class);
assertEquals(pinguo.name, pinguoDeserialized.name);
assertEquals(pinguo.seedCount, ((Apple) pinguoDeserialized).seedCount);
assertEquals(pinguo.type, ((Apple) pinguoDeserialized).type);
FruitWrapper pinguoWrapperDeserialized = MAPPER.readValue(pinguoWrapperJson, FruitWrapper.class);
Fruit pinguoExtracted = pinguoWrapperDeserialized.fruit;
assertTrue(pinguoExtracted instanceof Apple);
assertSame(pinguoExtracted.getClass(), Apple.class);
assertEquals(pinguo.name, pinguoExtracted.name);
assertEquals(pinguo.seedCount, ((Apple) pinguoExtracted).seedCount);
assertEquals(pinguo.type, ((Apple) pinguoExtracted).type);
Fruit[] fruits = MAPPER.readValue(fruitListJson, Fruit[].class);
assertEquals(2, fruits.length);
assertEquals(Apple.class, fruits[0].getClass());
assertEquals("apple", ((Apple) fruits[0]).type);
assertEquals(Orange.class, fruits[1].getClass());
assertEquals("orange", ((Orange) fruits[1]).type);
List<Fruit> f2 = MAPPER.readValue(fruitListJson,
new TypeReference<List<Fruit>>() { });
assertNotNull(f2);
assertTrue(f2.size() == 2);
assertEquals(Apple.class, f2.get(0).getClass());
assertEquals(Orange.class, f2.get(1).getClass());
}
/**
* Animals - serialization tests for abstract method in base class
*/
@Test
public void testExistingPropertySerializationAnimals() throws Exception
{
Map<String,Object> result = writeAndMap(MAPPER, beelzebub);
assertEquals(3, result.size());
assertEquals(beelzebub.name, result.get("name"));
assertEquals(beelzebub.furColor, result.get("furColor"));
assertEquals(beelzebub.getType(), result.get("type"));
result = writeAndMap(MAPPER, rover);
assertEquals(3, result.size());
assertEquals(rover.name, result.get("name"));
assertEquals(rover.boneCount, result.get("boneCount"));
assertEquals(rover.getType(), result.get("type"));
String beelzebubSerialized = MAPPER.writeValueAsString(beelzebub);
assertEquals(beelzebubSerialized, beelzebubJson);
String roverSerialized = MAPPER.writeValueAsString(rover);
assertEquals(roverSerialized, roverJson);
String animalWrapperSerialized = MAPPER.writeValueAsString(beelzebubWrapper);
assertEquals(animalWrapperSerialized, beelzebubWrapperJson);
String animalListSerialized = MAPPER.writeValueAsString(animalList);
assertEquals(animalListSerialized, animalListJson);
}
/**
* Animals - deserialization tests for abstract method in base class
*/
@Test
public void testSimpleClassAsExistingPropertyDeserializationAnimals() throws Exception
{
Animal beelzebubDeserialized = MAPPER.readValue(beelzebubJson, Animal.class);
assertTrue(beelzebubDeserialized instanceof Cat);
assertSame(beelzebubDeserialized.getClass(), Cat.class);
assertEquals(beelzebub.name, beelzebubDeserialized.name);
assertEquals(beelzebub.furColor, ((Cat) beelzebubDeserialized).furColor);
assertEquals(beelzebub.getType(), beelzebubDeserialized.getType());
AnimalWrapper beelzebubWrapperDeserialized = MAPPER.readValue(beelzebubWrapperJson, AnimalWrapper.class);
Animal beelzebubExtracted = beelzebubWrapperDeserialized.animal;
assertTrue(beelzebubExtracted instanceof Cat);
assertSame(beelzebubExtracted.getClass(), Cat.class);
assertEquals(beelzebub.name, beelzebubExtracted.name);
assertEquals(beelzebub.furColor, ((Cat) beelzebubExtracted).furColor);
assertEquals(beelzebub.getType(), beelzebubExtracted.getType());
@SuppressWarnings("unchecked")
List<Animal> animalListDeserialized = MAPPER.readValue(animalListJson, List.class);
assertNotNull(animalListDeserialized);
assertTrue(animalListDeserialized.size() == 2);
Animal cat = MAPPER.convertValue(animalListDeserialized.get(0), Animal.class);
assertTrue(cat instanceof Cat);
assertSame(cat.getClass(), Cat.class);
Animal dog = MAPPER.convertValue(animalListDeserialized.get(1), Animal.class);
assertTrue(dog instanceof Dog);
assertSame(dog.getClass(), Dog.class);
}
/**
* Cars - serialization tests for no abstract method or type variable in base class
*/
@Test
public void testExistingPropertySerializationCars() throws Exception
{
Map<String,Object> result = writeAndMap(MAPPER, camry);
assertEquals(3, result.size());
assertEquals(camry.name, result.get("name"));
assertEquals(camry.exteriorColor, result.get("exteriorColor"));
assertEquals(camry.getType(), result.get("type"));
result = writeAndMap(MAPPER, accord);
assertEquals(3, result.size());
assertEquals(accord.name, result.get("name"));
assertEquals(accord.speakerCount, result.get("speakerCount"));
assertEquals(accord.getType(), result.get("type"));
String camrySerialized = MAPPER.writeValueAsString(camry);
assertEquals(camrySerialized, camryJson);
String accordSerialized = MAPPER.writeValueAsString(accord);
assertEquals(accordSerialized, accordJson);
String carWrapperSerialized = MAPPER.writeValueAsString(camryWrapper);
assertEquals(carWrapperSerialized, camryWrapperJson);
String carListSerialized = MAPPER.writeValueAsString(carList);
assertEquals(carListSerialized, carListJson);
}
/**
* Cars - deserialization tests for no abstract method or type variable in base class
*/
@Test
public void testSimpleClassAsExistingPropertyDeserializationCars() throws Exception
{
Car camryDeserialized = MAPPER.readValue(camryJson, Camry.class);
assertTrue(camryDeserialized instanceof Camry);
assertSame(camryDeserialized.getClass(), Camry.class);
assertEquals(camry.name, camryDeserialized.name);
assertEquals(camry.exteriorColor, ((Camry) camryDeserialized).exteriorColor);
assertEquals(camry.getType(), ((Camry) camryDeserialized).getType());
CarWrapper camryWrapperDeserialized = MAPPER.readValue(camryWrapperJson, CarWrapper.class);
Car camryExtracted = camryWrapperDeserialized.car;
assertTrue(camryExtracted instanceof Camry);
assertSame(camryExtracted.getClass(), Camry.class);
assertEquals(camry.name, camryExtracted.name);
assertEquals(camry.exteriorColor, ((Camry) camryExtracted).exteriorColor);
assertEquals(camry.getType(), ((Camry) camryExtracted).getType());
@SuppressWarnings("unchecked")
List<Car> carListDeserialized = MAPPER.readValue(carListJson, List.class);
assertNotNull(carListDeserialized);
assertTrue(carListDeserialized.size() == 2);
Car result = MAPPER.convertValue(carListDeserialized.get(0), Car.class);
assertTrue(result instanceof Camry);
assertSame(result.getClass(), Camry.class);
result = MAPPER.convertValue(carListDeserialized.get(1), Car.class);
assertTrue(result instanceof Accord);
assertSame(result.getClass(), Accord.class);
}
// for [databind#1635]: simple usage
@Test
public void testExistingEnumTypeId() throws Exception
{
Bean1635 result = MAPPER.readValue(a2q("{'value':3, 'type':'A'}"),
Bean1635.class);
assertEquals(Bean1635A.class, result.getClass());
Bean1635A bean = (Bean1635A) result;
assertEquals(3, bean.value);
assertEquals(ABC.A, bean.type);
}
// for [databind#1635]: verify that `defaultImpl` does not block assignment of
// type id
@Test
public void testExistingEnumTypeIdViaDefault() throws Exception
{
Bean1635 result = MAPPER.readValue(a2q("{'type':'C'}"),
Bean1635.class);
assertEquals(Bean1635Default.class, result.getClass());
assertEquals(ABC.C, result.type);
}
// [databind#2785]
@Test
public void testCopyOfSubtypeResolver2785() throws Exception {
ObjectMapper objectMapper = new ObjectMapper().copy();
objectMapper.registerSubtypes(Impl2785.class);
Object result = objectMapper.readValue("{ \"packetType\": \"myType\" }", Base2785.class);
assertNotNull(result);
}
// [databind#3271]: verify that `null` token does not become "null" String
@Test
public void testDeserializationWithValidType() throws Exception {
Shape3271 deserShape = MAPPER.readValue("{\"type\":\"square\"}", Shape3271.class);
assertEquals("square", deserShape.getType());
}
@Test
public void testDeserializationWithInvalidType() throws Exception {
Shape3271 deserShape = MAPPER.readValue("{\"type\":\"invalid\"}", Shape3271.class);
assertEquals("invalid", deserShape.getType());
}
@Test
public void testDeserializationNull() throws Exception {
Shape3271 deserShape = MAPPER.readValue("{\"type\":null}", Shape3271.class);
assertNull(deserShape.getType()); // error: "expected null, but was:<null>"
}
// [databind#3251]: Double vs BigDecimal
@Test
public void test3251WithNewProperty() throws Exception
{
GenericWrapperWithNew3251<?> wrapper = new GenericWrapperWithNew3251<>(123.5);
String json = MAPPER.writeValueAsString(wrapper);
GenericWrapperWithNew3251<?> actualWrapper = MAPPER.readValue(json, GenericWrapperWithNew3251.class);
assertThat(actualWrapper).satisfies(it -> assertThat(it.getValue()).isEqualTo(123.5));
assertThat(actualWrapper.getValue()).isInstanceOf(Double.class);
assertThat(json).contains("\"value\":123.5");
}
@Test
public void test3251WithExistingProperty() throws Exception
{
GenericWrapperWithExisting3251<?> wrapper = new GenericWrapperWithExisting3251<>(123.5);
String json = MAPPER.writeValueAsString(wrapper);
GenericWrapperWithExisting3251<?> actualWrapper = MAPPER.readValue(json, GenericWrapperWithExisting3251.class);
assertThat(actualWrapper).satisfies(it -> assertThat(it.getValue()).isEqualTo(123.5));
assertThat(actualWrapper.getValue()).isInstanceOf(Double.class);
assertThat(json).contains("\"value\":123.5");
}
}