Define Jackson Wrapper Name from its Inner Class - java

I'm creating a common class to standardize my JSON structure as written below,
public class Wrapper<T> {
private SoaHeader soaHeader;
private T payload;
}
public class PayloadFoo {
private String foo;
}
public class PayloadBar {
private String bar;
}
public class main(){
var foo = new Wrapper<PayloadFoo>();
var bar = new Wrapper<PayloadBar>();
}
Then later the expected JSON result for both foo and bar are
{
"foo": {
"soaHeader": {},
"payload": {
"foo": ""
}
}
}
and
{
"bar": {
"soaHeader": {},
"payload": {
"bar": ""
}
}
}
Can Jackson do such task by put either #JsonTypeName or #JsonRootName annotation on the PayloadFoo and PayloadBar classes? or any suggestion how can I achieve this? Thankyou

Jackson can handle this by using the #JsonTypeName annotation on the PayloadFoo and PayloadBar classes and the #JsonTypeInfo annotation on the Wrapper class.
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
public class JsonSubTypesExample {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
public static void main(String[] args) throws JsonProcessingException {
Wrapper<PayloadFoo> payloadFooWrapper = new Wrapper<>(new SoaHeader(), new PayloadFoo("foo"));
Wrapper<PayloadBar> payloadBarWrapper = new Wrapper<>(new SoaHeader(), new PayloadBar("bar"));
System.out.println(OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(payloadFooWrapper));
System.out.println(OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(payloadBarWrapper));
}
#JsonSubTypes({
#JsonSubTypes.Type(value = PayloadFoo.class, name = "foo"),
#JsonSubTypes.Type(value = PayloadBar.class, name = "bar"),
})
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public static class Wrapper<T> {
private SoaHeader soaHeader;
private T payload;
}
#JsonTypeName("foo")
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public static class PayloadFoo {
private String foo;
}
#JsonTypeName("bar")
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public static class PayloadBar {
private String bar;
}
#Data
#ToString
public static class SoaHeader {
}
}

Related

How to serialize a class that extends TreeSet with Jackson?

Class A looks like this:
#EqualsAndHashCode(callSuper = true)
#Data
#AllArgsConstructor
public final class A extends TreeSet<B> {
private final a;
private b;
private c;
public A(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
}
}
Class B:
#Data
#AllArgsConstructor
#EqualsAndHashCode
#ToString
public final class B {
private final int x;
private final double y;
}
When I serialize a class A object using Jackson:
jsonString = objectMapper.writeValueAsString(class_a_object);
I get a json Array like this:
[
{
"x": 3,
"y": 3.23
},
{
"x": 4,
"y": 2.12
},...
]
but the member variables a,b,c are missing. Is there a way I can include them into the json string?
Jackson recognises class A as a collection and register CollectionSerializer to serialise A's instances. We can modify default serialiser and provide custom serialiser. We can use BeanSerializerModifier to do that and reuse collection serialiser in custom implementation. To generate valid JSON you need to provide property name for set values.
Example:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.type.CollectionType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.io.File;
import java.io.IOException;
import java.util.TreeSet;
public class ModifyCollectionSerializerApp {
public static void main(String[] args) throws IOException {
A a = new A(1, 2);
a.add(new B(22, 2.2));
a.add(new B(33, 3.3));
SimpleModule aModule = new SimpleModule();
aModule.setSerializerModifier(new ABeanSerializerModifier());
JsonMapper mapper = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.addModule(aModule)
.build();
String json = mapper.writeValueAsString(a);
System.out.println(json);
}
}
class ABeanSerializerModifier extends BeanSerializerModifier {
#Override
public JsonSerializer<?> modifyCollectionSerializer(SerializationConfig config, CollectionType valueType, BeanDescription beanDesc, JsonSerializer<?> serializer) {
return new AJsonSerializer(serializer);
}
}
class AJsonSerializer extends JsonSerializer<A> {
private final JsonSerializer valuesSerializer;
AJsonSerializer(JsonSerializer valuesSerializer) {
this.valuesSerializer = valuesSerializer;
}
#Override
public void serialize(A value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeNumberField("a", value.getA());
gen.writeNumberField("b", value.getB());
gen.writeFieldName("values");
valuesSerializer.serialize(value, gen, serializers);
gen.writeEndObject();
}
}
#EqualsAndHashCode(callSuper = true)
#Data
#AllArgsConstructor
class A extends TreeSet<B> {
private final int a;
private final int b;
}
#Data
#AllArgsConstructor
#EqualsAndHashCode
#ToString
class B implements Comparable<B> {
private final int x;
private final double y;
#Override
public int compareTo(B o) {
return this.x - o.x;
}
}
Above code prints:
{
"a" : 1,
"b" : 2,
"values" : [ {
"x" : 22,
"y" : 2.2
}, {
"x" : 33,
"y" : 3.3
} ]
}

Jackson de-serialization doesn't work when both polymorphic types and builders are used

The given test fails, but I think it shouldn't.
When the data objects are converted to regular non-builder classes, the test passes (source: https://pastebin.com/pBkTb6HW).
To make the test pass with builder objects, one has to add the #JsonTypeInfo annotation on the Animal interface. This means that the Zoo cannot be fully generic, but needs a common supertype for all animals.
It seems like this difference shouldn't exist?
Jackson version: 2.10
Error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `JacksonTest$Animal` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (String)"{"animals":[{"#type":"Dog","name":"doggo"},{"#type":"Cat","name":"cat"}]}"; line: 1, column: 13] (through reference chain: JacksonTest$Zoo$Builder["animals"]->java.util.ArrayList[0])
Full test case:
public class JacksonTest {
#Test
void test() throws JsonProcessingException {
ObjectMapper m = new ObjectMapper();
m.findAndRegisterModules();
List<Animal> animals = List.of(
Dog.builder().name("doggo").build(),
Cat.builder().name("cat").build()
);
Zoo z = Zoo.builder().animals(animals).build();
String json = m.writeValueAsString(z);
Zoo deser = m.readValue(json, Zoo.class);
assertThat(z).isEqualTo(deser);
}
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = Zoo.Builder.class)
static class Zoo {
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
List<Animal> animals;
#JsonPOJOBuilder(withPrefix = "")
public static class Builder {}
}
interface Animal {
String getName();
}
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = Dog.Builder.class)
static class Dog implements Animal {
String name;
#JsonPOJOBuilder(withPrefix = "")
public static class Builder {}
}
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = Cat.Builder.class)
static class Cat implements Animal {
String name;
#JsonPOJOBuilder(withPrefix = "")
public static class Builder {}
}
}
Since we want to avoid adding to the Animals interface we can solve this by adding an interface, ZooBuilder, we then add the #JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) annotation to the animals mutator method and have Zoo.Builder implement ZooBuilder.
Versions:
AdoptOpenJDK 14
Eclipse: 2020-03 (4.15.0)
junit: 5.6.2
log4j2: 2.13.3
jackson: 2.11.0
lombok: 1.18.12
package io.jeffmaxwell.stackoverflow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
#Log4j2
public class Q62193465 {
static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
#BeforeAll
static void findAndRegistrerModules() {
OBJECT_MAPPER.findAndRegisterModules();
}
#Test
void test() throws JsonProcessingException {
List<Animal> animals = List.of(Dog.builder()
.name("doggo")
.build(),
Cat.builder()
.name("cat")
.build());
var zoo = Zoo.builder()
.animals(animals)
.build();
var zooAsJsonString = OBJECT_MAPPER.writeValueAsString(zoo);
LOGGER.info("zooAsJsonString {}", zooAsJsonString);
var zooFromJsonString = OBJECT_MAPPER.readValue(zooAsJsonString, Zoo.class);
assertEquals(zoo, zooFromJsonString);
}
//Added
interface ZooBuilder {
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
Zoo.Builder animals(final List<Animal> animals);
}
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = Zoo.Builder.class)
public static class Zoo {
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
List<Animal> animals;
#JsonPOJOBuilder(withPrefix = "")
public static class Builder implements ZooBuilder {
}
}
interface Animal {
String getName();
}
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = Dog.Builder.class)
static class Dog implements Animal {
String name;
#JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = Cat.Builder.class)
static class Cat implements Animal {
String name;
#JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
}
Alternative Solution
This avoids the interface but is more risky as it interacts with more of the Lombok generated code.
package io.jeffmaxwell.stackoverflow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
#Log4j2
public class Q62193465 {
static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
#BeforeAll
static void findAndRegistrerModules() {
OBJECT_MAPPER.findAndRegisterModules();
}
#Test
void test() throws JsonProcessingException {
List<Animal> animals = List.of(Dog.builder()
.name("doggo")
.build(),
Cat.builder()
.name("cat")
.build());
var zoo = Zoo.builder()
.animals(animals)
.build();
var zooAsJsonString = OBJECT_MAPPER.writeValueAsString(zoo);
LOGGER.info("zooAsJsonString {}", zooAsJsonString);
var zooFromJsonString = OBJECT_MAPPER.readValue(zooAsJsonString, Zoo.class);
assertEquals(zoo, zooFromJsonString);
}
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = Zoo.Builder.class)
public static class Zoo {
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
List<Animal> animals;
#JsonPOJOBuilder(withPrefix = "")
public static class Builder {
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public Builder animals(List<Animal> animals) {
this.animals = animals;
return this;
}
}
}
interface Animal {
String getName();
}
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = Dog.Builder.class)
static class Dog implements Animal {
String name;
#JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = Cat.Builder.class)
static class Cat implements Animal {
String name;
#JsonPOJOBuilder(withPrefix = "")
public static class Builder {
}
}
}

How ignore NullNode for deserialization JSON in JAVA

So the problem is the next:
I have POJO like:
#Data
#Accessors(chain = true)
#JsonInclude(Include.NON_NULL)
public class TestPOJO {
private Long id;
private String name;
private JsonNode jsonNode;
Also I have json like
{
"id":1
"name": "foo"
"jsonNode":null
}
When I try deserialize the last one by the
ObjectMapper objectMapper = new ObjectMapper();
TestPOJO testPojo = objectMapper.readValue(<json String>, TestPOJO.class);
I get testPojo object where jsonNode field is NullNode, but I need in testPojo == null
How I can fix it?
Add a class that extends JsonDeserializer<JsonNode> and that returns null if parser.getText() is null:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
public class JsonNodeDeserializer extends JsonDeserializer<JsonNode> {
#Override
public JsonNode deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
String value = parser.getText();
if(value == null) {
return null;
}
return (JsonNode) context.findRootValueDeserializer(context.constructType(JsonNode.class)).deserialize(parser, context);
}
}
Then annotate the jsonNode attribute with #JsonDeserialize(using = JsonNodeDeserializer.class) to tell Jackson to use your custom deserializer:
#Data
#Accessors(chain = true)
#JsonInclude(Include.NON_NULL)
public class TestPOJO {
private Long id;
private String name;
#JsonDeserialize(using = JsonNodeDeserializer.class)
private JsonNode jsonNode;
// getters and setters
}

Get Derived DTO From Base Class Request Body DTO

I try to get derived class fields from methods response body. Request body parameter is type of base class. Request comes with derived class fields but I can't cast it to derived class.
Here is my controller method and DTO classes:
Method:
#PostMapping("/{code}")
public ResponseEntity<PromotionDto> createPromotion(#PathVariable String code, #RequestBody PromotionDto promotion){
if(PromotionTypeEnum.ORDER_THRESHOLD_DISCOUNT.equals(promotion.getPromotionType())) {
promotionService.createPromotion(orderThresholdDiscountPromotionConverter.toEntity((OrderThresholdDiscountPromotionDto)promotion));
}
return ResponseEntity.ok(promotion);
}
Base class DTO:
import dto.base.BaseDto;
import promotionservice.PromotionTypeEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
#Data
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public class PromotionDto extends BaseDto {
private String code;
private String title;
private String description;
private PromotionTypeEnum promotionType;
}
Derived class DTO:
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
#Data
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public class OrderThresholdDiscountPromotionDto extends PromotionDto {
private Double thresholdTotal;
private Double discountPrice;
private String messageFired;
private String messageCouldHaveFired;
}
Request JSON is:
{
"code":"qwewe",
"title":"qwewe",
"description":"qwewe",
"promotionType":"ORDER_THRESHOLD_DISCOUNT",
"thresholdTotal":1.3,
"discountPrice":"12.5",
"messageFired":"qwewe",
"messageCouldHaveFired":"qwewe"
}
as result, service returns error:
{
"type": "https://www.jhipster.tech/problem/problem-with-message",
"title": "Internal Server Error",
"status": 500,
"detail": "promotion.PromotionDto cannot be cast to promotion.OrderThresholdDiscountPromotionDto",
"path": "/api/promotionresults/qwewe",
"message": "error.http.500"
}
My question is: is there any way, library, annotation etc. to get the
derived class instance from request ?
Use Jackson inheritance feature. Annotate PromotionDto class as below:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "promotionType")
#JsonSubTypes({
#Type(value = OrderThresholdDiscountPromotionDto.class, name = "ORDER_THRESHOLD_DISCOUNT"),
})
class PromotionDto {
and remove:
private PromotionTypeEnum promotionType;
property. It will be handled automatically by Jackson. In controller you will be able to use instanceof.
What are you trying to do is you are trying typecast the Parent into a child which is known as Downcasting. This is only valid when you have the Parent as an instance of child. In your case, PromotionDto should be an instance of OrderThresholdDiscountPromotionDto.
Please refer below example:
public class PromotionDto {
private String code;
private String title;
private String description;
public static void main(String[] args) {
PromotionDto promotionDto = new OrderThresholdDiscountPromotionDto();
PromotionDto promotionDto_2 = new PromotionDto();
//Valid downcasting
OrderThresholdDiscountPromotionDto subClass1 = (OrderThresholdDiscountPromotionDto)promotionDto;
//Invalid down casting
OrderThresholdDiscountPromotionDto subClass2 = (OrderThresholdDiscountPromotionDto)promotionDto_2;
}
}
class OrderThresholdDiscountPromotionDto extends PromotionDto {
private Double thresholdTotal;
private Double discountPrice;
private String messageFired;
private String messageCouldHaveFired;
}

Include fields with custom annotation with JacksonAnnotationIntrospector.hasIgnoreMarker()

I want to include only fields in my classes that have my custom annotation #MyInclude but Jackson ends up ignoring everything. What am I doing wrong?
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.setAnnotationIntrospector(new IgnoreIntrospector());
MyNestedObject nestedObject = new MyNestedObject("value1", "value2");
MyObject object = new MyObject();
object.setNestedObject(nestedObject);
String json = mapper.writeValueAsString(object); //This returns {}
}
public static class IgnoreIntrospector extends JacksonAnnotationIntrospector {
private static final long serialVersionUID = -3951086067314107368L;
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return !m.hasAnnotation(MyInclude.class) || super.hasIgnoreMarker(m);
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class MyObject {
#MyInclude
private MyNestedObject nestedObject;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class MyNestedObject {
#MyInclude
private String value1;
private String value2;
}
mapper.writeValueAsString(object) is returning {} but it should return NestedObject with value1 populated instead (ignoring value2).
If I update my IgnoreIntrospector.hasIgnoreMarker () to just super.hasIgnoreMarker(m) then everything would be included in the json string.
The IgnoreIntrospector alone wasn't enough. Since my custom annotations were only on fields, I needed to disable all visibility:
mapper.setAnnotationIntrospector(new IgnoreIntrospector());
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
mapper.setVisibility(mapper.getDeserializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
And updated my IgnoreIntrospector:
public static class IgnoreIntrospector extends JacksonAnnotationIntrospector {
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return m instanceof AnnotatedField && _findAnnotation(m, MyInclude.class) == null;
}
}
Alternatively, override both hasIgnoreMarker() and findNameForSerialization():
JsonMapper jsonMapper = JsonMapper.builder()
.annotationIntrospector(new JacksonAnnotationIntrospector()
{
#Override
public boolean hasIgnoreMarker(AnnotatedMember m)
{
return m.hasAnnotation(CustomIgnore.class) || super.hasIgnoreMarker(m);
}
#Override
public PropertyName findNameForSerialization(Annotated a)
{
if(a.hasAnnotation(CustomIgnore.class)) return null;
return super.findNameForSerialization(a);
}
})
.build();

Categories