Annotation #JsonTypeInfo is used. When serializing as a list/array, property field isn't generated.
Assuming this is not a bug in Jackson, what's missing to get this working?
Raised an issue in Jackson Databind: https://github.com/FasterXML/jackson-databind/issues/3656
Sample code to demonstrate the issue
Jackson: 2.14.0
JDK: 17
// Base.java
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface Base {
String getName();
void setName(String name);
}
// BaseContainer.java
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface BaseContainer extends Base {
List<Base> getItems();
void setItems(List<Base> items);
}
// BaseImpl.java
public class BaseImpl implements Base {
private String name;
#Override
public String getName() {
return name;
}
#Override
public void setName(String name) {
this.name = name;
}
}
// ContainerImpl.java
public class ContainerImpl implements BaseContainer {
private String name;
private List<Base> items;
#Override
public String getName() {
return name;
}
#Override
public void setName(String name) {
this.name = name;
}
#Override
public List<Base> getItems() {
return items;
}
#Override
public void setItems(List<Base> items) {
this.items = items;
}
}
class SerTest {
private static ObjectMapper objectMapper;
#BeforeAll
static void beforeAll() {
objectMapper = new ObjectMapper();
}
#Test
void testSingleBase() {
Base item1 = new BaseImpl();
item1.setName("item1");
String expected = """
{"type":"BaseImpl","name":"item1"}""";
String actual = assertDoesNotThrow(() -> objectMapper.writeValueAsString(item1));
assertEquals(expected, actual);
}
#Test
void testSingleContainer() {
Base item1 = new BaseImpl();
item1.setName("item1");
BaseContainer container1 = new ContainerImpl();
container1.setName("container1");
container1.setItems(List.of(item1));
String expected = """
{"type":"ContainerImpl","name":"container1","items":[{"type":"BaseImpl","name":"item1"}]}""";
String actual = assertDoesNotThrow(() -> objectMapper.writeValueAsString(container1));
assertEquals(expected, actual);
}
// This test fails
// Expected :[{"type":"ContainerImpl","name":"container1","items":[{"type":"BaseImpl","name":"item1"}]},{"type":"BaseImpl","name":"item2"}]
// Actual :[{"name":"container1","items":[{"type":"BaseImpl","name":"item1"}]},{"name":"item2"}]
#Test
void testList() {
Base item1 = new BaseImpl();
item1.setName("item1");
Base item2 = new BaseImpl();
item2.setName("item2");
BaseContainer container1 = new ContainerImpl();
container1.setName("container1");
container1.setItems(List.of(item1));
List<Base> items = new ArrayList<>();
items.add(container1);
items.add(item2);
String expected = """
[{"type":"ContainerImpl","name":"container1","items":[{"type":"BaseImpl","name":"item1"}]},{"type":"BaseImpl","name":"item2"}]""";
String actual = assertDoesNotThrow(() -> objectMapper.writeValueAsString(items));
assertEquals(expected, actual);
}
}
Related
I have two Java classes:
public class Request
{
private List<Item> subItems;
public Request()
{
}
public List<Item> getSubItems()
{
return subItems;
}
public void setSubItems(List<Item> subItems)
{
this.subItems = subItems;
}
}
class Item
{
private String name;
private String functionName;
//...elided...
}
The subItems that will be passed can be complex (include a function) or simple (just a name). There can be a mix of these. To simplify the JSON, I'd like to be able to accept the following:
JSON:
{
"subItems": [
{
"name": "complexType",
"function": "someFunction"
},
"simpleType"
]
}
and then have this turned into the equivalent of the following instance:
Request request = new Request();
request.setSubItems(
Arrays.asList(
new Item( "complexType", "SomeFunction" ),
new Item( "simpleType" )
)
);
Is this possible with Jackson/ObjectMapper?
What settings and annotations would I need?
If your Item class has a string constructor, it will be called with the "simpleType" value.
class Item {
private String name;
private String functionName;
public Item() {
}
public Item(String name) {
this.name = name;
}
// getters and setters here
}
Full demo
public class Request {
public static void main(String[] args) throws IOException {
String json = "{\"subItems\":[" +
"{\"name\":\"complexType\",\"functionName\":\"SomeFunction\"}," +
"\"simpleType\"" +
"]}";
Request request = new ObjectMapper().readValue(json, Request.class);
System.out.println(request);
}
private List<Item> subItems;
public Request() {
}
public Request(Item... subItems) {
this.subItems = Arrays.asList(subItems);
}
public List<Item> getSubItems() {
return this.subItems;
}
public void setSubItems(List<Item> subItems) {
this.subItems = subItems;
}
#Override
public String toString() {
return "Request [subItems=" + this.subItems + "]";
}
}
class Item {
private String name;
private String functionName;
public Item() {
}
public Item(String name) {
this.name = name;
}
public Item(String name, String functionName) {
this.name = name;
this.functionName = functionName;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getFunctionName() {
return this.functionName;
}
public void setFunctionName(String functionName) {
this.functionName = functionName;
}
#Override
public String toString() {
return "Item [name=" + this.name + ", functionName=" + this.functionName + "]";
}
}
Output
Request [subItems=[Item [name=complexType, functionName=SomeFunction], Item [name=simpleType, functionName=null]]]
With Jackson, I need to convert an instance of my class Test in CSV but I'm getting problems with a class that contains one list (Inner)
Ex:
public class Test {
String testName;
#JsonUnwrapped
Simple simple;
#JsonUnwrapped
Inner inner;
public Test(String testName, Simple simple, Inner inner) {
this.testName = testName;
this.simple = simple;
this.inner = inner;
}
public String getTestName() {
return testName;
}
public void setTestName(String testName) {
this.testName = testName;
}
public Simple getSimple() {
return simple;
}
public void setSimple(Simple simple) {
this.simple = simple;
}
public Inner getInner() {
return inner;
}
public void setInner(Inner inner) {
this.inner = inner;
}
}
class Inner {
#JsonUnwrapped
List<Person> persons;
public Inner(List<Person> persons) {
this.persons = persons;
}
public List<Person> getPersons() {
return persons;
}
public void setPersons(List<Person> persons) {
this.persons = persons;
}
}
class Person {
String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Simple {
String simpleName;
public Simple(String simpleName) {
this.simpleName = simpleName;
}
public String getSimpleName() {
return simpleName;
}
public void setSimpleName(String simpleName) {
this.simpleName = simpleName;
}
}
class Main {
public static void main(String[] args) {
Simple simple = new Simple("simple");
Person person = new Person("jesus");
Inner inner = new Inner(Arrays.asList(person));
Test test = new Test("test", simple, inner);
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(Test.class);
try {
String csv = mapper.writer(schema).writeValueAsString(test);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
For objects properties, I used the annotation #JsonUnwrapped as recommended on this link ,
but I get one exception when jackson try to convert the list Inner.persons:
How can I fix that ?
I have the following class that has to be serialized to JSON and back to the class instance:
public class Container {
private List<Base> derivedOne;
private List<Base> derivedTwo;
#JsonCreator
public Container(#JsonProperty("derivedOne") List<Base> derivedOne,
#JsonProperty("derivedTwo") List<Base> derivedTwo) {
this.derivedOne = derivedOne;
this.derivedTwo = derivedTwo;
}
public static class Derived1 extends Base {
private String derivedField1;
public Derived1(String derivedField1) {
this.derivedField1 = derivedField1;
}
}
public static class Derived2 extends Base {
private String derivedField2;
public Derived2(String derivedField2) {
this.derivedField2 = derivedField2;
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include =
JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Derived1.class, name = "one"),
#JsonSubTypes.Type(value = Derived2.class, name = "two")
})
public abstract static class Base {
}
}
so that derivedOne container is going to hold only Derived1.class instances, and derivedTwo - only Derived2.class instances.
Is there a way in Jackson not to use the extra type property, to determine the target class by the including container name?
I was trying to get it working with a custom TypeIdResolver but without success.
Depends on your json value.
You can use type to indicate the type of deserialization.
Below is the complete code.
public class Container {
private List<Base> derivedOne;
private List<Base> derivedTwo;
#JsonCreator
public Container(#JsonProperty("derivedOne") List<Base> derivedOne,
#JsonProperty("derivedTwo") List<Base> derivedTwo) {
this.derivedOne = derivedOne;
this.derivedTwo = derivedTwo;
}
public static class Derived1 extends Base {
private String derivedField1;
public String getDerivedField1() {
return derivedField1;
}
public void setDerivedField1(String derivedField1) {
this.derivedField1 = derivedField1;
}
}
public static class Derived2 extends Base {
private String derivedField2;
public String getDerivedField2() {
return derivedField2;
}
public void setDerivedField2(String derivedField2) {
this.derivedField2 = derivedField2;
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include =
JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Derived1.class, name = "one"),
#JsonSubTypes.Type(value = Derived2.class, name = "two")
})
public abstract static class Base {
}
public static void main(String[] args) throws IOException {
String jsonStr = "{\"derivedOne\":[{\"type\":\"one\",\"derivedField1\":\"derivedField1\"},{\"type\":\"two\",\"derivedField2\":\"derivedField2\"}],\"derivedTwo\":[{\"type\":\"one\",\"derivedField1\":\"derivedField1\"},{\"type\":\"two\",\"derivedField2\":\"derivedField2\"}]}";
ObjectMapper objectMapper = new ObjectMapper();
Container container = objectMapper.readValue(jsonStr, Container.class);
}
}
use #JsonTypeIdResolver.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include =
JsonTypeInfo.As.PROPERTY, property = "type")
#JsonTypeIdResolver(ContainerResolver.class)
public abstract static class Base {
}
public static void main(String[] args) throws IOException {
String jsonStr = "{\"derivedOne\":[{\"type\":\"one\",\"derivedField1\":\"derivedField1\"},{\"type\":\"two\",\"derivedField2\":\"derivedField2\"}],\"derivedTwo\":[{\"type\":\"one\",\"derivedField1\":\"derivedField1\"},{\"type\":\"two\",\"derivedField2\":\"derivedField2\"}]}";
ObjectMapper objectMapper = new ObjectMapper();
Container container = objectMapper.readValue(jsonStr, Container.class);
}
public class ContainerResolver extends TypeIdResolverBase {
private JavaType superType;
#Override
public void init(JavaType baseType) {
this.superType = baseType;
}
#Override
public String idFromValue(Object value) {
return idFromValueAndType(value, value.getClass());
}
#Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
String typeId = null;
switch (suggestedType.getSimpleName()) {
case "Derived1":
typeId = "one";
break;
case "Derived2":
typeId = "two";
}
return typeId;
}
#Override
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
Class<?> subType = null;
switch (id) {
case "one":
subType = Container.Derived1.class;
break;
case "two":
subType = Container.Derived2.class;
}
return context.constructSpecializedType(superType, subType);
}
#Override
public JsonTypeInfo.Id getMechanism() {
return JsonTypeInfo.Id.NAME;
}
}
This article may be helpful to you.
I need to create a Map from java bean such that the key is prefixed with name of the java bean variable. I am using jackson for this. Example given below:
public class Address{
String city;
String state;
//setters and getters
}
Address address = new Address();
address.setCity("myCity");
address.setState("myState");
I am creating map using following:
ObjectMapper objectMapper = new ObjectMapper();
Map map = objectMapper.convertValue(address, HashMap.class);
Which gives me following output:
{"city":"myCity", "state":"myState"}
I need to add class variable name to the key as shown below:
{"address.city":"myCity", "address.state":"myState"}
How do I achieve that?
If you have jackson-annotations enabled:
public class Address{
#JsonProperty("address.city")
String city;
#JsonProperty("address.state")
String state;
//setters and getters
}
read more about it here: https://github.com/FasterXML/jackson-annotations
It is possible to customise bean serialization by registering a BeanSerializerModifier. This specifically supports renaming properties by applying a NameTransformer to each BeanPropertyWriter.
#Test
public void prepend_class_name_to_property_keys() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Function<Class<?>, String> classPrefix = clazz -> clazz.getSimpleName().toLowerCase() + ".";
mapper.registerModule(new Module() {
#Override
public String getModuleName() {
return "Example";
}
#Override
public Version version() {
return Version.unknownVersion();
}
#Override
public void setupModule(SetupContext context) {
context.addBeanSerializerModifier(new BeanSerializerModifier() {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
String prefix = classPrefix.apply(beanDesc.getBeanClass());
return beanProperties.stream().map(prop -> prop.rename(new NameTransformer() {
#Override
public String transform(String name) {
return prefix + name;
}
#Override
public String reverse(String transformed) {
return transformed.substring(prefix.length());
}
})).collect(toList());
}
});
}
});
assertThat(mapper.writeValueAsString(new Address("somewhere", "someplace")),
equivalentTo("{ 'address.line1' : 'somewhere', 'address.line2' : 'someplace'}"));
}
public static final class Address {
public final String line1;
public final String line2;
public Address(String line1, String line2) {
this.line1 = line1;
this.line2 = line2;
}
}
Jackson 2.2.3
First, please excuse the stupid mistakes, I'm on a disconnected network, so I had to retype manually)
I have the following XML:
<orgs>
<org name="Test1">
<item>a</item>
<item>b</item>
</org>
<org name="Test2">
<item>c</item>
<item>d</item>
<item>e</item>
</org>
</orgs>
I have the following class to parse this:
#XmlRootElement(name = "orgs")
#XmlAccessorType(XmlAccessType.FIELD)
public class XmlOrgElements {
private List<Org> orgs;
public List<Org> getOrgs() {
return orgs;
}
public void setOrg(List<Org> orgs) {
this.orgs = orgs;
}
public class Org {
#JacksonXmlProperty(isAttribute = true)
private String name;
private List<Item> items;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Item> getItems() {
return items;
}
public void setName(List<Item> items) {
this.items = items;
}
}
public class Item {
#JacksonXmlText
private String item;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
}
}
But all I'm getting back is "orgs=null". Does anyone know why?
You need to enable unwrapped handling for lists; default is to use "wrapped" format. The best way to diagnose this problem is to start with Java objects, serialize as XML, and see what the output format is.
This gives an idea of how structure differs.
If you want to default to unwrapped style, you can use:
JacksonXmlModule module = new JacksonXmlModule();
module.setDefaultUseWrapper(false);
mapper.registerModule(module);
There is also an annotation #JacksonXmlElementWrapper:
public class Bean {
#JacksonXmlElementWrapper(useWrapping=false)
public List<Stuff> entry;
}
to change behavior on per-list-property basis.
Here is the answer for those reading along:
#JacksonXmlRootElement(localname = "orgs")
public class Orgs {
#JacksonXmlElementWrapper(useWrapping = false)
private List<Org> org;
public List<Org> getOrg() {
return org;
}
public void setOrg(List<Org> org) {
this.orgs = org;
}
public Orgs() {}
}
public class Org {
#JacksonXmlProperty(isAttribute = true)
private String name;
#JacksonXmlElementWrapper(useWrapping = false)
private List<String> item;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getItem() {
return item;
}
public void setItem(List<String> item) {
this.item = item;
}
}