In given class Base which is extended by Ext class. Serialization works perfect but issue is when trying to deserialize the serialized string back to Ext class. I want to deserialize back to Ext class including the all the Base class fields.
#Data, #NonFinal, #Value are all lombok annotations.
#Data
public class Base {
private String foo;
public Base( String foo) {
this.foo = foo;
}
}
#Value
#NonFinal
public class Ext extends Base {
private String bar;
public Ext(String foo, String bar) {
super(foo);
this.bar = bar;
}
}
Method to Deserialize
#Test
void shouldDeserialize() throws IOException {
ObjectMapper mapper = new ObjectMapper();
Ext ext = new Ext("foo", "bar");
String serializedExt = mapper.writeValueAsString(ext);
System.out.println(serializedExt); // {"foo":"foo","bar":"bar"}
// Throws err
base = mapper.readValue(serializedExt, Ext.class);
}
Error: com.fasterxml.jackson.databind.exc.InvalidDefinitionException:Cannot construct instance of ..Inhertence.Ext(no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (String)"{"foo":"foo","bar":"bar"}"; line: 1, column: 2]
The error message is indicative : in your class the default constructor is not present and you haven't annotated its constructor with the JsonCreator annotation. You can deserialize your class annotating its constructor :
#Value
#NonFinal
public class Ext extends Base {
private String bar;
#JsonCreator
public Ext(#JsonProperty("foo") String foo, #JsonProperty("bar") String bar) {
super(foo);
this.bar = bar;
}
}
Related
I'd like to serialize/deserialize (json) a class that contains an attribute that is an interface, but the underlying class doesn't have any attributes. The below is my most simplified case and my best attempt at what to do.
This throws an error when trying to deserialize No suitable constructor found for type [simple type, class com.example.Bar]: can not instantiate from JSON object (need to add/enable type information?) at [Source: java.io.StringReader#301ec38b; line: 1, column: 2]
public interface FooInterface {
String doThing();
}
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
#EqualsAndHashCode
public class Foo implements FooInterface {
#Override
public String doThing() {
return "foo";
}
}
#Getter
#Setter
#EqualsAndHashCode
public class Bar {
FooInterface foo;
public Bar(FooInterface foo) {
this.foo = foo;
}
}
#Test
public void fooTest() throws IOException {
Foo foo = new Foo();
Bar bar = new Bar(foo);
String serialized = new ObjectMapper().writeValueAsString(bar); // = {"foo":{}}
Bar deserialized = new ObjectMapper().readValue(serialized, Bar.class);
Assert.assertEquals(bar, deserialized);
}
Please add default constructor to class Bar and I guess your issue should be resolved.
#Getter
#Setter
#EqualsAndHashCode
public class Bar {
FooInterface foo;
public Bar() {}
public Bar(FooInterface foo) {
this.foo = foo;
}
}
Do let me know if this doesn't solve your problem, I will try to dig deeper.
As #Aditya mentioned I was missing the default constructor which was causing the error I was having, but then the new error led me to finding this question which was the crux of the problem that this question was asking about.
Looks like I misunderstood what the JsonAutoDetect annotation did. Below is the code that ended up working for me.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class),
})
public interface FooInterface {
String doThing();
}
#EqualsAndHashCode
public class Foo implements FooInterface {
#Override
public String doThing() {
return "foo";
}
}
#Getter
#Setter
#EqualsAndHashCode
public class Bar {
FooInterface foo;
public Bar() {}
public Bar(FooInterface foo) {
this.foo = foo;
}
}
#Test
public void fooTest() throws IOException {
Foo foo = new Foo();
Bar bar = new Bar(foo);
String serialized = new ObjectMapper().writeValueAsString(bar); // {"foo":{"type":"Foo"}}
Bar deserialized = new ObjectMapper().readValue(serialized, Bar.class);
Assert.assertEquals(bar, deserialized);
}
I've got two POJOs:
#lombok.Value
public class Foo {
String foo;
Bar bar;
}
#lombok.Value
public class Bar {
String bar;
String baz;
}
I'd like to be able to deserialize the following to a Foo instance:
{
"foo": "some foo",
"bar": "{ \"bar\": \"some bar\", \"baz\": \"some baz\" }"
}
If I understand it correctly this the exact opposite #JsonRawValue. There, it convert a Java String value (which is valid JSON value) to JSON object. But here, I need to convert a JSON string value to Java object.
I suspect that I need to write a custom deserializer, but I'm not sure how exactly since it involves parsing the raw JSON and assign it to the field. Maybe BeanDeserializerModifier? (I have no idea how to use it.)
I'd like to keep the object immutable (#Value), but I can drop this requirement if it helps solving the problem.
Thanks.
if you use FooDto class delete this Bar bar; from DtoClass
or use this
#lombok.Value
public class Foo {
String foo;
#JsonIgnore
Bar bar;
}
With the help of this question mentioned by #MichaĆ Ziober I managed to do it. Posting it here for reference since it was kinda tricky to get it working (with #Value and CamelCase property naming in JSON):
{
"Foo": "some foo",
"Bar": "{ \"bar\": \"some bar\", \"baz\": \"some baz\" }"
}
And the implementation is:
#Value
#Builder
#JsonDeserialize(builder = Foo.FooBuilder.class)
public class Foo {
#JsonAlias("Foo") // note this!
String foo;
Bar bar;
#JsonPOJOBuilder(withPrefix = "")
public static class FooBuilder {
#JsonAlias("Bar") // note this!
#JsonDeserialize(using = BarDeserializer.class)
public FooBuilder bar(Bar bar) {
this.bar = bar;
return this;
}
}
}
#lombok.Value
public class Bar {
String bar;
String baz;
}
public class BarDeserializer extends StdDeserializer<Bar> {
public BarDeserializer() {
super(Bar.class);
}
#Override
public Bar deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
return (Bar) ((ObjectMapper) p.getCodec()).readValue(p.getValueAsString(), _valueClass);
}
}
I have pojo objects with inheritance and generics like this:
child object:
#Data
#ToString(callSuper = true)
#EqualsAndHashCode(callSuper = true)
public class MessageCreatedEvent extends AbstractEvent<MessageDto> {
#JsonCreator
public MessageCreatedEvent(MessageDto data) {
super(data);
}
}
parent:
#Data
public abstract class AbstractEvent<T> {
private final UUID id = UUID.randomUUID();
private T data;
public AbstractEvent(T data) {
this.data = data;
}
}
and content holding object:
#Data
public class MessageDto implements Serializable {
private UUID id;
private String content;
// and other fields
}
and jackson configuration which is used in rabbitTemplate:
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
At start I didn't use #JsonCreator property but when I receive json message from RabbitMQ and tried it deserialize in rabbit handler I got this error:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of MessageCreatedEvent (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
After this I added #JsonCreator but then properties in MessageDto object are not set. There is only id field filled and others are null.
Can you tell me what I have wrong configured? Thank you.
EDIT:
I try modified pojos and remove generic data field from parent, and move it into child, now deserialization working, so it looks like that Jackson has som problem with generics. Any idea?
I copied your classes in a sample spring boot app (I am not using Lombok but replicating its behaviour) and doing the following and is working
public class Testing {
public static void main(String[] args) throws
JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
MessageDto data = new MessageDto(UUID.randomUUID(),
"someContent");
MessageCreatedEvent createdEvent = new MessageCreatedEvent();
createdEvent.setData(data);
String json = objectMapper.writeValueAsString(createdEvent);
System.out.println(json);
MessageCreatedEvent fromJson =
objectMapper.readValue(json, MessageCreatedEvent.class);
System.out.println(fromJson.getData().getContent());
System.out.println(fromJson.getData().getId());
}
}
Added a no argument constructor to MessageDto
Added a no argument constructor to AbstractEvent
Removed the following and is empty class:
#JsonCreator
public MessageCreatedEvent(MessageDto data) {
super(data);
}
I'm not sure if it can help, but you may need to use #JsonProperty. To be more specific, please refer to https://thepracticaldeveloper.com/2016/10/23/produce-and-consume-json-messages-with-spring-boot-amqp/
I'm experimenting with Jackson deserialization for inheritance in Java.
I've a base class:
#Getter //Lombok #Getter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value=ClassA.class, name = "classA")
})
public abstract class BaseClass {
private List<String> fields;
#JsonCreator
public BaseClass(#JsonProperty("fields") final List<String> fields) {
this.fields = fields;
}
}
ClassA is also abstract
#Getter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "typeA", include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value=SubClassA.class, name = "subclassA")
})
public abstract class ClassA extends BaseClass{
private String mode;
#JsonCreator
public ClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode) {
super(fields);
this.mode = mode;
}
}
My subClassA:
public class SubClassA extends ClassA {
private String dummyField;
public SubClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode,
#JsonProperty("dummyField") String dummyField) {
super(fields, mode);
this.dummyField = dummyField;
}
}
If I pass in a JSON of in the following form:
{
"type": "classA",
"typeA": "subclassA",
"mode": "testingMode",
"fields": ["1", "2"],
"dummyField": "dummy"
}
I get an error Cannot construct instance of ClassA (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
I came across this https://github.com/FasterXML/jackson-databind/issues/374 which says this is a known issue with Jackson.
How do I go about writing a customDeserializer for this.
In classA I tried doing this:
#JsonDeserialize(using = ClassADeserializer.class)
and ClassADeserializer is:
public class ClassADeserializer extends StdDeserializer<ClassA> {
private final JsonDeserializer<?> defaultDeserializer;
public ClassADeserializer(JsonDeserializer<?> defaultDeserializer) {
super(ClassA.class);
this.defaultDeserializer = defaultDeserializer;
}
#Override public ClassA deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return (ClassA) defaultDeserializer.deserialize(jsonParser, deserializationContext);
}
which obviously doesn't work. How do I go about writing a custom deserializer for this?
Problem:
You pass in json "type": "classA",... That means jackson first try to create instance of ClassA..During deserialization jackson search #JsonCreator constructor first..If #JsonCreator missing or can not call #JsonCreator constructor then jackson create object with default constructor and call setter method... In your ClassA #JsonCreator constructor with 2 arguments but jackson call with 3 arguments.. So its fail. then jackson call default constructor to create instance. but default constructor also missing.. thats why u get this error: Cannot construct instance of ClassA (no Creators, like default construct, exist)..
Solution:
As you want to deserialize to SubClassA... You need to use #JsonCreator in SubClassA...Then you need to use #JsonIgnoreProperties to ignore properties type so that jackson create instance of SubClassA instead of ClassA....
Try with below SubClassA:
#Getter
#JsonIgnoreProperties(ignoreUnknown = true)
public class SubClassA extends ClassA {
private String dummyField;
#JsonCreator
public SubClassA(#JsonProperty("fields") final List<String> fields, #JsonProperty("mode") String mode,
#JsonProperty("dummyField") String dummyField) {
super(fields, mode);
this.dummyField = dummyField;
}
}
I've got two classes:
public class Bar {
private String identifier;
private String otherStuff;
public Bar(){}
public Bar(String identifier, String otherStuff) {
this.identifier = identifier;
this.otherStuff = otherStuff;
}
// Getters and Setters
}
and
public class Foo {
private String foo;
#JsonSerialize(using=BarsMapSerializer.class)
#JsonDeserialize(using=BarsMapDeserializer.class)
private Map<String, Bar> barsMap;
public Foo(){}
public Foo(String foo, Map<String, Bar> barsMap) {
this.foo = foo;
this.barsMap = barsMap;
}
// Getters and Setters
}
When I sserialize Foo with code:
public static void main(String[] args) throws Exception {
Map<String, Bar> barsMap = new LinkedHashMap<>();
barsMap.put("b1", new Bar("bar1", "nevermind1"));
barsMap.put("b2", new Bar("bar2", "nevermind2"));
barsMap.put("b3", new Bar("bar3", "nevermind3"));
Foo foo = new Foo("foo", barsMap);
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(foo);
System.out.println(jsonString);
}
the otput is:
{"foo":"foo","barsMap":{"b1":"bar1","b2":"bar2","b3":"bar3"}}
For most cases it's ok, but in some cases I want to have full Bar object in my json, like bellow:
{"foo":"foo","barsMap":{"b1":{"identifier":"bar1", "otherStuff":"nevermind1"},"b2":{"identifier":"bar2", "otherStuff":"nevermind2"},"b3":{"identifier":"bar3", "otherStuff":nevermind3"}}}
Is it possible to achieve this without writing custom serializer?
I know that I can add annotation using mix-in mechanism, but basically I need to ignore existing one in some cases.
I've resolved my problem using mix-in mechanism.
public interface FooMixin {
#JsonSerialize
Map<String, Bar> getBarsMap();
#JsonDeserialize
void setBarsMap(Map<String, Bar> barsMap);
}
With this interface mixed in, class Foo is serialized as with default serializer.
As You can see You need to add JsonSerialize / JsonDeserialize annotations without specify any class.
Below code shows usage of this interface:
mapper = new ObjectMapper();
mapper.addMixInAnnotations(Foo.class, FooMixin.class);
jsonString = mapper.writeValueAsString(foo);
System.out.println(jsonString);