ExternalPropertyWithArrayShape4277Test.java
package tools.jackson.databind.jsontype;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.*;
import tools.jackson.databind.*;
import tools.jackson.databind.exc.InvalidDefinitionException;
import tools.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for issue #4277: Combining {@code @JsonFormat(shape=ARRAY)} with
* {@code @JsonTypeInfo(include=EXTERNAL_PROPERTY)} should fail with a clear
* error message, as these features are architecturally incompatible.
*/
public class ExternalPropertyWithArrayShape4277Test extends DatabindTestUtil
{
// Base classes for polymorphism
static class Animal {
public String name;
protected Animal() { }
public Animal(String n) { name = n; }
}
@JsonTypeName("cat")
static class Cat extends Animal {
public Cat() { }
public Cat(String n) { super(n); }
}
@JsonTypeName("dog")
static class Dog extends Animal {
public Dog() { }
public Dog(String n) { super(n); }
}
// Test class combining ARRAY shape with EXTERNAL_PROPERTY (should fail)
@JsonFormat(shape = JsonFormat.Shape.ARRAY)
@JsonPropertyOrder({"type", "uniqueId", "animal"})
static class WrapperWithExternalProperty {
public String type;
public String uniqueId;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "cat"),
@JsonSubTypes.Type(value = Dog.class, name = "dog")
})
public Animal animal;
protected WrapperWithExternalProperty() { }
public WrapperWithExternalProperty(String type, String id, Animal a) {
this.type = type;
this.uniqueId = id;
this.animal = a;
}
}
// Test class with ARRAY shape and PROPERTY inclusion (should work)
@JsonFormat(shape = JsonFormat.Shape.ARRAY)
@JsonPropertyOrder({"uniqueId", "animal"})
static class WrapperWithPropertyInclusion {
public String uniqueId;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "cat"),
@JsonSubTypes.Type(value = Dog.class, name = "dog")
})
public Animal animal;
protected WrapperWithPropertyInclusion() { }
public WrapperWithPropertyInclusion(String id, Animal a) {
this.uniqueId = id;
this.animal = a;
}
}
// Test class with ARRAY shape and WRAPPER_ARRAY inclusion (should work)
@JsonFormat(shape = JsonFormat.Shape.ARRAY)
@JsonPropertyOrder({"uniqueId", "animal"})
static class WrapperWithWrapperArrayInclusion {
public String uniqueId;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.WRAPPER_ARRAY)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "cat"),
@JsonSubTypes.Type(value = Dog.class, name = "dog")
})
public Animal animal;
protected WrapperWithWrapperArrayInclusion() { }
public WrapperWithWrapperArrayInclusion(String id, Animal a) {
this.uniqueId = id;
this.animal = a;
}
}
private final ObjectMapper MAPPER = jsonMapperBuilder()
.registerSubtypes(Cat.class, Dog.class)
.build();
/*
/**********************************************************************
/* Test methods
/**********************************************************************
*/
/**
* Main test for issue #4277: combining EXTERNAL_PROPERTY with ARRAY shape
* should fail with a clear error message.
*/
@Test
public void testExternalPropertyWithArrayShapeFailsClearly() throws Exception
{
// Try to deserialize - should fail with clear error
String json = "[\"cat\",\"id123\",{\"name\":\"Fluffy\"}]";
InvalidDefinitionException e = assertThrows(InvalidDefinitionException.class, () -> {
MAPPER.readValue(json, WrapperWithExternalProperty.class);
});
// Verify error message is present
assertNotNull(e.getMessage());
}
/**
* Verify that the error message contains helpful information about
* why the combination doesn't work and what alternatives exist.
*/
@Test
public void testErrorMessageContainsAlternatives() throws Exception
{
String json = "[\"cat\",\"id123\",{\"name\":\"Fluffy\"}]";
InvalidDefinitionException e = assertThrows(InvalidDefinitionException.class, () -> {
MAPPER.readValue(json, WrapperWithExternalProperty.class);
});
String msg = e.getMessage();
// Check that error mentions both features
assertTrue(msg.contains("ARRAY") || msg.contains("array"),
"Error should mention ARRAY shape: " + msg);
assertTrue(msg.contains("EXTERNAL_PROPERTY") || msg.contains("external"),
"Error should mention EXTERNAL_PROPERTY: " + msg);
// Check that error mentions at least one alternative
assertTrue(msg.contains("PROPERTY") || msg.contains("WRAPPER_ARRAY") ||
msg.contains("alternative") || msg.contains("custom deserializer"),
"Error should mention alternatives: " + msg);
}
/**
* Verify that ARRAY shape works fine with PROPERTY inclusion (type ID inside object).
*/
@Test
public void testArrayShapeWithPropertyInclusion() throws Exception
{
WrapperWithPropertyInclusion input = new WrapperWithPropertyInclusion("id123",
new Cat("Fluffy"));
// Serialize
String json = MAPPER.writeValueAsString(input);
// JSON should be an array with embedded type
assertTrue(json.startsWith("["), "Should be JSON array");
// Deserialize
WrapperWithPropertyInclusion result = MAPPER.readValue(json,
WrapperWithPropertyInclusion.class);
assertNotNull(result);
assertEquals("id123", result.uniqueId);
assertNotNull(result.animal);
assertInstanceOf(Cat.class, result.animal);
assertEquals("Fluffy", result.animal.name);
}
/**
* Verify that ARRAY shape works fine with WRAPPER_ARRAY inclusion.
*/
@Test
public void testArrayShapeWithWrapperArrayInclusion() throws Exception
{
WrapperWithWrapperArrayInclusion input = new WrapperWithWrapperArrayInclusion("id123",
new Cat("Fluffy"));
// Serialize
String json = MAPPER.writeValueAsString(input);
// JSON should be an array
assertTrue(json.startsWith("["), "Should be JSON array");
// Deserialize
WrapperWithWrapperArrayInclusion result = MAPPER.readValue(json,
WrapperWithWrapperArrayInclusion.class);
assertNotNull(result);
assertEquals("id123", result.uniqueId);
assertNotNull(result.animal);
assertInstanceOf(Cat.class, result.animal);
assertEquals("Fluffy", result.animal.name);
}
}