I have a base class (Foo) with 2 children (A and B). They look like this:
public abstract class Foo {
private String fooString;
public Foo(String fooString) {
this.fooString = fooString;
}
//getter
}
#JsonDeserialize(builder = A.ABuilder.class)
public class A extends Foo {
private int amount;
public A(String fooString, int amount) {
super(fooString);
this.amount = amount;
}
//getter
#JsonPOJOBuilder
public static class ABuilder {
private String fooString;
private int amount;
public ABuilder withFooString(final String fooString) {
this.fooString = fooString;
return this;
}
public ABuilder withAmount(final int amount) {
this.amount = amount;
return this;
}
public A build() {
return new A(fooString, amount);
}
}
}
#JsonDeserialize(builder = B.BBuilder.class)
public class B extends Foo {
private String type;
public B(String fooString, String type) {
super(fooString);
this.type = type;
}
//getter
#JsonPOJOBuilder
public static class BBuilder {
private String fooString;
private String type;
public BBuilder withFooString(final String fooString) {
this.fooString = fooString;
return this;
}
public BBuilder withType(final String type) {
this.type = type;
return this;
}
public B build() {
return new B(fooString, type);
}
}
}
In my controller I have this endpoint:
#PutMapping
private ResponseEntity<Foo> doSomething(#RequestBody Foo dto) {
//stuff
}
But whenever I try to send over my json payload:
{
"fooString":"test",
"amount":1
}
I get the error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.test.Foo` (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)"{"fooString":"test","amount":1}; line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1027)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
at AbstractJackson.main(AbstractJackson.java:11)
How do I get jackson to deserialize the json into the proper child class? What am I doing wrong?
The base class won't get the constructors of the sub classes instead it is quite the opposite,you cannot set subclass specific properties in base class instead you need to use specific subclass for the call or use custom deserializer for base class with correct use intanceOf
The easiest way to get it working is to change the controller method.
#PutMapping
private ResponseEntity<Foo> doSomething(#RequestBody A dto) {
//stuff
}
Related
Using Jackson, I want to deserialize some values into generic wrapper objects for which I have a specific static factory method for each type.
However, Jackson does not seem to pick up on this layer of indirection, even if I annotate the factory methods with #JsonCreator.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of Wrapped (no Creators, like default constructor, exist): no String-argument constructor/factory method to deserialize from String value ('Carl')
How can I make Jackson use the factory methods that return wrappers with a generic type?
This self-contained code illustrates my problem:
class Request {
// I want to deserialize into these fields
#JsonProperty Wrapped<Person> person;
#JsonProperty Wrapped<Score> score;
}
class Wrapped<T> {
// This is my generic wrapper type.
// Its construction is non-trivial: it is impossible to first construct the value before wrapping it.
// Therefor, construction is performed by the factory methods of the concrete value classes (Person, Score, ...).
// Let's say for simplicity that it did have a simple constructor:
T value;
public Wrapped(T value) {
this.value = value;
}
}
class Person {
#JsonCreator
public static Wrapped<Person> createWrapped(String name) {
// complex construction of the wrapped person
return new Wrapped<>(new Person(name));
}
#JsonValue
String name;
public Person(String name) {
this.name = name;
}
}
class Score {
#JsonCreator
public static Wrapped<Score> createWrapped(int score) {
// complex construction of the wrapped score
return new Wrapped<>(new Score(score));
}
#JsonValue
int score;
public Score(int score) {
this.score = score;
}
}
class Example {
private static final String JSON_REQUEST =
"""
{
"person":"Carl",
"score":20
}
""";
public static void main(String[] args) throws Exception {
Request request = new ObjectMapper()
.readValue(JSON_REQUEST, Request.class);
System.out.println(request.person.value.name);
System.out.println(request.score.value.score);
}
}
It is important to note that type information is only in the java classes, it should not be in the json.
One solution, add a DTO:
public class RequestDTO {
#JsonValue
String person;
#JsonValue
Integer score;
/**
* #return the person
*/
public String getPerson() {
return person;
}
/**
* #return the score
*/
public Integer getScore() {
return score;
}
/**
* #param person the person to set
*/
public void setPerson(String person) {
this.person = person;
}
/**
* #param score the score to set
*/
public void setScore(Integer score) {
this.score = score;
}
public RequestDTO() {
}
public RequestDTO(String person, Integer score) {
this.person = person;
this.score = score;
}
}
And change Request definition to use Mode.DELEGATING
public class Request {
// I want to deserialize into these fields
#JsonProperty Wrapped<Person> person;
#JsonProperty Wrapped<Score> score;
#JsonCreator(mode=Mode.DELEGATING)
public static Request createWrapped(RequestDTO requestDTO) {
// complex construction of the wrapped person
Request req = new Request();
req.person = new Wrapped<>(new Person(requestDTO.getPerson()));
req.score = new Wrapped<>(new Score(requestDTO.getScore()));
return req ;
}
}
#p3consulting's answer sent me in the right direction, but it lead to something completely different.
Jackson has something called a Converter that does exactly what I want.
I created converters for each wrapped value type,
and then annotated the properties in the request to use those converters:
class Request {
#JsonDeserialize(converter = WrappedPersonConverter.class)
Wrapped<Person> person;
#JsonDeserialize(converter = WrappedScoreConverter.class)
Wrapped<Score> score;
}
class WrappedPersonConverter
extends StdConverter<String, Wrapped<Person>> {
#Override
public Wrapped<Person> convert(String value) {
return Person.createWrapped(value);
}
}
class WrappedScoreConverter
extends StdConverter<Integer, Wrapped<Score>> {
#Override
public Wrapped<Score> convert(Integer score) {
return Score.createWrapped(score);
}
}
For factory methods with more complex signatures, you can make this work by using a DTO, e.g.:
class WrappedPersonConverter2
extends StdConverter<WrappedPersonConverter2.DTO, Wrapped<Person>> {
#Override
public Wrapped<Person> convert(WrappedPersonConverter2.DTO dto) {
return Person.createWrapped(dto.first, dto.last);
}
public static class DTO {
public int first;
public int last;
}
}
I cannot believe this was so simple but took me so long to find.
Room is not finding setType method defined in the parent class. Gives cannot find setter for field error during compilation.
Parent class
public class Data {
private int type = -1;
public Data() {
}
public int getType() {
return type;
}
public Data setType(int type) {
this.type = type;
return this;
}
}
Child class
#Entity
public class Log extends Data {
#PrimaryKey(autoGenerate = true)
public int id;
public Log() {
}
}
Usually setters do not return values.
Change your setType() method to:
public void setType(int type) {
this.type = type;
}
P.S. Obviously, returning the same instance of Data object is useless here, since you're invoking the method on that object and already have it.
If you want to keep the builder pattern, you could consider using an internal static class for that matter as follows (you don't need an empty constructor, that's added implicitly):
public class Data {
private int type = -1;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public static class Builder {
private Data data = new Data();
public Builder setType(int type) {
data.setType(type);
return this;
}
public Data build() {
return data;
}
}
}
Now, for creating a data class you could do:
Data data = new Data.Builder()
.setType(10)
.build();
Let me introduce my code then I will ask a question.
This is just an example. I would like to learn something new if it is possbile.
BaseClass.java
public class BaseClass {
private String baseName;
BaseClass(String baseName){
this.baseName = baseName;
}
//getters and setters
}
MyClass.java
public class MyClass extends BaseClass {
private boolean isTest;
private String name;
MyClass(){
}
MyClass(String baseName){
super(baseName);
this.isTest = true;
}
//getters and setters
}
MyClassController.java
#Controller
public class MyClassController {
#GetMapping(value="/")
#ResponseBody
public String myClassController(#RequestBody MyClass myClass) {
return "index";
}
}
JSON request:
{
"name": "Name for BaseClass"
}
So, I send name e.g.: Name for BaseClass. I want to set this name for variable BaseName in BaseClass through constructor. #RequestBody needs no atribute constructor so I cannot use there this second constructor with arguments. I can handle this e.g. for using additional method:
Additional method in MyClass.java
public MyClass setValues(String baseName){
super(baseName);
this.isTest = true;
return this;
}
New MyController.java
#Controller
public class MyClassController {
#GetMapping(value="/")
#ResponseBody
public String myClassController(#RequestBody MyClass myClass) {
myClass.setValues(myClass.getName());
//more uses for myClass
return "index";
}
}
Is there any better way to do something like this in more "professional" way?
If you're married to the current inheritance structure, you can use HttpMessageConverter to customize the way Spring deserializes HTTP requests.
public class MyClassConverter extends AbstractHttpMessageConverter<MyClass> {
public MyClassConverter() {
super(new MediaType("text", "myClass"));
}
#Override
protected boolean supports(Class<?> clazz) {
return MyClass.class.isAssignableFrom(clazz);
}
#Override
protected MyClass readInternal(Class<? extends MyClass> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// Deserialize JSON request
MyClass inputObject = new MyClass(name);
return inputObject;
}
#Override
protected void writeInternal(MyClass myClass, HttpOutputMessage outputMessage) {
// Serialize MyClass object
}
}
Detailed example
Although it's not clear I'm assuming name and baseName are meant to be the same value. In that case it might make sense for BaseClass to be an abstract class or interface.
abstract class:
public class MyClass extends BaseClass {
private String name;
// constructors
#Override
String getName() {
return name;
}
// setters
}
public abstract class BaseClass {
abstract String getName();
}
interface:
public class MyClass implements DtoWithName {
private String name;
// constructors
#Override
String getName() {
return name;
}
// setters
}
public interface DtoWithName {
String getName();
}
Also, I can't tell much about your use-case from the given example, but you should read into Composition over inheritance to make sure you're going about it the right way. With DTOs in particular usually simple is best.
public interface A extends C {
String getCh();
String getId();
String getReview();
}
public interface B extends C {
String getCh();
String getId();
String getReview();
}
#Data
#Builder
public class AImpl implements A{
private String ch;
private String id;
private String review;
}
#Data
#Builder
public class BImpl implements B{
private String ch;
private String id;
private String review;
}
so now to use the builders of these I do:
return AImpl.builder()
.ch("ch")
.id("id")
.review("somerview");
For B I do:
return BImpl.builder()
.ch("ch1")
.id("id1")
.review("some new review");
Is there a way where I can make this builder part into a function? I dont like the idea of repeating the same code again. Like where I can pass id channel and review in a function and I can the object?
Disclaimer: I have never really dealt with builders so there might be a really much better option :D
This approach writes builders for each interface individually.
This does require that the interfaces provide a setter method.
Using generics, the methods of the RootBuilder and BaseABuilder return an instance of the ImplABuilder so that the chain can continue properly.
This is a very simple implementation of the Thistype generic which in other languages exists by default. This implementation also relies on casting to the actual Thistype but if you set the generics properly, that shouldnt be an issue.
public class Test
{
public static void main(String[] args)
{
ImplA implA = ImplA
.builder()
.id("id")
.description("description")
.valueA("a")
.build();
}
}
public interface Root
{
String getId();
void setId(String id);
String getDescription();
void setDescription(String description);
}
public class RootBuilder<Thistype extends RootBuilder<Thistype, Instance>, Instance extends Root>
{
protected final Instance object;
RootBuilder(Instance object)
{
this.object = object;
}
public Thistype id(String value)
{
object.setId(value);
return (Thistype)this;
}
public Thistype description(String value)
{
object.setDescription(value);
return (Thistype)this;
}
public Instance build()
{
return object;
}
}
public interface BaseA extends Root
{
String getValueA();
void setValueA(String valueA);
}
public class BaseABuilder<Thistype extends BaseABuilder<Thistype, Instance>, Instance extends BaseA> extends RootBuilder<Thistype, Instance>
{
protected Instance object;
BaseABuilder(Instance object)
{
super(object);
}
public Thistype valueA(String value)
{
object.setValueA(value);
return (Thistype)this;
}
}
public interface BaseB extends Root
{
String getValueB();
void setValueB(String valueB);
}
public interface BaseC extends Root
{
String getValueC();
void setValueC(String valueC);
}
public final class ImplA implements BaseA
{
private String id;
private String description;
private String valueA;
private ImplA() { }
public static ImplABuilder builder()
{
return new ImplABuilder(new ImplA());
}
private static class ImplABuilder extends BaseABuilder<ImplABuilder, ImplA> // assuming ImplA is final
{
ImplABuilder(ImplA object)
{
super(object);
}
// additional methods for ImplA class
}
}
I currently have my POJO class as such for deserializing a json source.
public class OpenBuilding extends Building {
#JsonProperty("BuildingPostCode")
#Override
public String getPostcode() {
return super.getPostcode();
}
}
Where the parent class is as such
public abstract class Buidling {
protected String postcode;
public String getPostcode() {
return this.postcode;
}
}
My issue is that the String postcode isn't getting mapped at all. It works when using the annotation on the field. However since it is an inherited field and I have other children of Building, which use different property names for the same data, I cannot have it implemented in that way.
For example:
public class DirectedBuilding extends Building {
#JsonProperty("Pseudo_PostCode")
#Override
public String getPostcode() {
return super.getPostcode();
}
}
Perhaps try defining a constructor with #JsonCreator.
class Parent {
private final String foo;
public Parent(final String foo) {
this.foo = foo;
}
public String getFoo() {
return foo;
}
}
class Child extends Parent {
#JsonCreator
public Child(#JsonProperty("foo") final String foo) {
super(foo);
}
#JsonProperty("foo")
public String getFoo() {
return super.getFoo();
}
}
public static void main(String[] args) throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
final Child toSerialize = new Child("fooValue");
// Serialize the object to JSON
final String json = objectMapper.writer()
.withDefaultPrettyPrinter()
.writeValueAsString(toSerialize);
// Prints { "foo" : "fooValue" }
System.out.println(json);
// Deserialize the JSON
final Child deserializedChild = objectMapper.readValue(json, Child.class);
// Prints fooValue
System.out.println(deserializedChild.getFoo());
}