I'm using JSON Jackson on SpringBoot context.
I have a problem with Jackson deserialization with Generic type constraint.
This code work fine :
public abstract class AbstractSyncableWriteAndReadResource<T, K> extends AbstractSyncableReadResource<T, K> {
...
#Timed
#RequestMapping(method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
#Transactional
public ResponseEntity<List<SynQueuDTO<T>>> syncPushPut(#RequestBody List<SynQueuDTO<T>> entities) {
...
}
}
But this code don't work :
This code work fine :
public abstract class AbstractSyncableWriteAndReadResource<T extends EntitySyncableWrite, K> extends AbstractSyncableReadResource<T, K> {
...
#Timed
#RequestMapping(method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
#Transactional
public ResponseEntity<List<SynQueuDTO<T>>> syncPushPut(#RequestBody List<SynQueuDTO<T>> entities) {
...
}
}
There is only one difference : add Java interface on Generic for constraint Class's type.
Exception :
Type definition error: [simple type, class xxx.xxx.xxx.EntitySyncableWrite]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `xxx.xxx.xxx.EntitySyncableWrite` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information\n at [Source: (PushbackInputStream); line: 5, column: 26]
I try a lot of configurations to force Jackson take Class instead of Interface informations but without success :
#JsonTypeInfo(use= JsonTypeInfo.Id.NAME, include= JsonTypeInfo.As.WRAPPER_OBJECT)
or #JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") (ko)
Do you have some idea?
Thanks
[UPDATE]
An another use case that work very well :
public abstract class AbstractSyncableWriteAndReadResource<T extends EntitySyncableWrite, K> ... {
public ResponseEntity<List<SyncQueuDTO<T>>> syncPushPut( #RequestBody SyncQueuDTO<T> data) {
...
}
}
But don't work when add RequestBody as List :
public abstract class AbstractSyncableWriteAndReadResource<T extends EntitySyncableWrite, K> ... {
public ResponseEntity<List<SyncQueuDTO<T>>> syncPushPut( #RequestBody List<SyncQueuDTO<T>> data) {
...
}
}
Exception :
Type definition error: [simple type, class xx.xx.xx.EntitySyncableWrite]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `xx.xx.xx.EntitySyncableWrite` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information\n at [Source: (PushbackInputStream); line: 5, column: 26] (through reference chain: java.util.ArrayList[0]->xx.xx.xx.SyncQueuDTO[\"serverVersion\"])
With Map JSON array of objects to #RequestBody List<T> using jackson inspiration I solved this Jackson/JVM limitation as :
public abstract class AbstractSyncableWriteAndReadResource<T extends EntitySyncableWrite, K> extends AbstractSyncableReadResource<T, K> {
...
public ResponseEntity<List<SyncQueuDTO<T>>> syncPushPut(#RequestBody SyncQueuDTOList<T> queuList) {
....
}
}
and ArrayList implementation :
public class SyncQueuDTOList<T extends EntitySyncableWrite> extends ArrayList<SyncQueuDTO<T>> {}
Related
I have two java endpoints in spring boot like this:
#PostMapping(path="/my-import-1")
#ResponseStatus(HttpStatus.OK)
public String myImport1(#Valid #RequestBody ParameterDto1 params) {
return this.serviceImpl.import(params);
}
and
#PostMapping(path="/my-import-2")
#ResponseStatus(HttpStatus.OK)
public String myImport2(#Valid #RequestBody ParameterDto2 params) {
return this.serviceImpl.import(params);
}
Both use the same service for importing, but have some differences in their parameters.
I created the service's import method like this
#Override
public String import(ParameterInterface params) throws Exception {
...
}
and the ParameterInterface like this
public interface ImportMetaData {
public default ArrayList<FileInterface> getFiles() {
return null;
}
public void setFiles(ArrayList<FileInterface> files);
}
Implementing this interface I created two ParameterDto classes (ParameterDto1 and ParameterDto2). The IDE shows everything is correct, also the start of my service works, but as soon as I send a request to one of the endpoints, I get the following error:
Servlet.service() for servlet [dispatcherServlet] in context with path
[] threw exception [Request processing failed; nested exception is
org.springframework.http.converter.HttpMessageConversionException:
Type definition error: [simple type, class
com.beo.services.myImportService.rest.domain.dto.metadata.interfaces.ParameterInerface];
nested exception is
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of
com.beo.services.myImportService.rest.domain.dto.metadata.interfaces.ParameterInerface
(no Creators, like default constructor, exist): abstract types either
need to be mapped to concrete types, have custom deserializer, or
contain additional type information at [Source:
(PushbackInputStream); line: 3, column: 5] (through reference chain:
com.beo.services.myImportService.rest.domain.dto.metadata.ParameterDto["files"]->java.util.ArrayList[0])]
with root cause
Can I any how create such an ArrayList from an interface and get these two endpoints running? Or is there another solution?
The issue is with the ParameterDto1 and ParameterDto2. Jackson library requires a default, no-args constructor or a constructor with parameters annotated with #JsonProperty("field_name"), otherwise it cannot convert your message.
Solution:
Add a no-args constructor to ParameterDto1 and ParameterDto2 or annotate the constructor parameters with #JsonProperty("field_name")
im guessing here because you didnt share the implementation of ParameterDto1 or ParameterDto2 - and for some reason your interface is called ImportMetaData where according to the exception, your explanation and other files it should be ParameterInterface.
the problem is that getFiles/setFiles is considered as a property by jackson , its type is an interface and you are not sending any type information.
in general assuming ParameterDto1 and ParameterDto2are using a concreate implementation of FileInterface you could just change your interface methods getFiles/setFiles so they are using generics parameter and in each implementation set the concreate type for FileInterface you are using , this will allow jackson to understand the concreate type for FileInterface .
incase ParameterDto1 and ParameterDto2 are not using a concreate implementation of FileInterface you should add #JsonTypeInfo or #JsonSubTypes (see https://www.baeldung.com/jackson-annotations section 5 for more info) - note that the client calling the api should also specify the actual type in the json-type field
Suggested implementation
public interface ParameterInterface {
#JsonIgnore
public List<FileInterface> getParameters() default { return null;}
.....
}
public class ParameterDto1 implements ParameterInterface {
private List<FileImpl1> files;
public List<FileImpl1> getFiles(){return files;}
public void setFiles(List<FileImpl1> files){this.files=files;}
....
}
public class ParameterDto2 implements ParameterInterface {
private List<FileImpl2> files;
public List<FileImpl2> getFiles(){return files;}
public void setFiles(List<FileImpl2> files){this.files=files;}
...
}
public class FileImpl1 implements FileInterface{
...
}
public class FileImpl2 implements FileInterface{
...
}
MyClass.java:
class MyClass<T extends Serializable> implements Serializable{
#JsonProperty(value = "allowedValues", required = false)
private List<T> allowedValues;
}
I'm getting this exception while deserializing this above object persisted in the DB.
How do i resolve this?
Error while de-serializing json to MyClass.java: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of java.io.Serializable (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)"{"allowedValues":["a","b","c"]} (through reference chain: com.babu.model.MyClass->["allowedValues"]->java.util.ArrayList[0])
I'm creating an utility in Spring Boot to connect and insert/upsert data into the couchbase in the more generic way possible.
I have something like this:
public interface GenericRepository extends CouchbaseRepository<MyClass, String> {
}
Where I have MyClass I would like to accept any kind of document to insert into couchbase.
I have tried some things like using the generic type T but without success because I got the following error:
Caused by: org.springframework.data.mapping.MappingException: Couldn't
find PersistentEntity for type class java.lang.Object!
My structure is: service (interface/impl) > DAO (interface/impl) > repository
Extra info: Across the above model I am passing a Generic type T. I am calling the service with my Pojo with the #Document annotation.
The goal is to remove the "dependency" of having one repository class for each type of Document.
You can have generic repositories (bit hacky) but with some limitation. Assume you have documents;
#Document
public class MyClass1 extends BaseClass {
private String text1;
public MyClass1() {
super();
setType(Type.MyClass1);
}
// getter/setters
}
#Document
public class MyClass2 extends BaseClass {
private String text2;
public MyClass2() {
super();
setType(Type.MyClass2);
}
// getter/setters
}
with BaseClass;
#Document
public class BaseClass {
private Type type;
// other common fields if any, and getter/setters
public enum Type {
MyClass1, MyClass2
}
}
Then you can have the following repository;
public interface GenericRepository<T extends BaseClass> extends CouchbaseRepository<T, String> {
public List<T> findByType(BaseData.Type type);
public default List<T> findAll(Class<T> clazz) throws IllegalAccessException, InstantiationException {
return findByType(clazz.newInstance().getType());
}
}
and use it like;
#Autowired
private GenericRepository<MyClass1> mc1Repository;
#Autowired
private GenericRepository<MyClass2> mc2Repository;
public void doStuff() {
MyClass1 myClass1 = new MyClass1();
myClass1.setText1("text1");
mc1Repository.save(myClass1);
mc1Repository.findAll(MyClass1.class).forEach(d -> System.out.println(d.getText1()));
MyClass2 myClass2 = new MyClass2();
myClass2.setText2("text2");
mc2Repository.save(myClass2);
mc2Repository.findAll(MyClass2.class).forEach(d -> System.out.println(d.getText2()));
}
will print out;
text1
text2
But be aware that the documents will be all in same collection that is the collection for the BaseClass
Also this won't work with more than one extension (like MyClass1 extends Mid1, and Mid1 extends Base)
UPDATE: You can build a class that does generic manipulation of an entity (save/delete/update) via the CouchbaseOperations class. All you need to do is to inject it in your service or custom repository.
I don't think this is possible via the Spring SDK (Couchbase just implements the Spring's spec). However, you can create a single generic repository using reflection and the standard Java SDK:
Cluster cluster = CouchbaseCluster.create("localhost");
cluster.authenticate("username", "password");
Bucket bucket = cluster.openBucket("bucketname");
// Create a JSON Document
JsonObject arthur = JsonObject.create()
.put("name", "Arthur")
.put("email", "kingarthur#couchbase.com")
.put("interests", JsonArray.from("Holy Grail", "African Swallows"));
// Store the Document
bucket.upsert(JsonDocument.create("u:king_arthur", arthur));
I want to integrate vavr validation library in my command dto's in a way that when command dto is deserialized from request, return type of the static factory will be Try but jackson is throwing following error :
Type definition error: [simple type, class com.foo.command.FooCommand]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.foo.command.FooCommand (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
Here is FooCommand
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public final class FooCommand {
private String foo;
private String bar;
#JsonCreator
public static Try<FooCommand> of(
#JsonProperty("foo") String foo,
#JsonProperty("bar") String bar
) {
return Try.of(() -> {
//Validate values
});
}
}
I am using spring 5 and it's annotated to deserialize request body automatically into controller parameter.
Is something like this possible ? Thanks in advance.
I had a similar problem that I fixed by using Converters: Using Jackson, how can I deserialize values using static factory methods that return wrappers with a generic type?
I haven't yet found how to apply the converters automatically, so you have to annotate every occurrence of the wrapped type in your requests.
public class Request {
#JsonDeserialize(converter = FooCommandConverter.class)
Try<FooCommand> command;
}
You can write a Converter like so:
public class FooCommandConverter
extends StdConverter<FooCommandConverter.DTO, Try<FooCommand>> {
#Override
public Try<FooCommand> convert(FooCommandConverter.DTO dto) {
return FooCommand.of(
dto.foo,
dto.bar
);
}
public static class DTO {
public String foo;
public String bar;
}
}
So I have a class that I want jackson to serialize.
public class MyClass<T extends MyInterface> {
private T myGeneric;
private String desc;
....
}
public interface MyInterface {
String getSomeString();
}
public class MySubClass implements MyInterface {
private String info;
....
#Override
public getSomeString() {
return "";
}
}
MyClass can have many types of other classes under it's myGeneric field.
The problem is that when I pass my JSON to the server, then Jackson throws an error: problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information.
I investigated around and mostly only found examples of how to solve jackson problems with abstract classes but none for this kind of problem. I also tried using the #JsonTypeInfo and #JsonSubTypes annotations to list what kind of classes can go under MyClass but I am not sure if what I did was just wrong or not because it is hard to find any similar examples with them and the documentation in here: was not really helpful also.
Is this kind of problem solvable with Jackson annotations or I need to write a custom serializer for my class?
I am testing the serialization like this:
String json = "myJson";
ObjectMapper mapper = new ObjectMapper();
MyClass myClass = mapper.readValue(json, MyClass.class);
Jackson can't deserialize abstract types without additional info: when you have JSON with field
"myGeneric" : { "field1" : 1, "field2" : 2}
you have no idea what is the class of the myGeneric object.
So you have two options: use #JsonTypeInfo annotation or to create custom deserializer. Example:
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#class")
private T myGeneric ;
After that, serialized myGeneric field will look something like that:
"myGeneric" : { "field1" : 1, "field2" : 2, "#class" : "com.project.MySubClass"}
Deserializer will use this info to instantiate an object of correct type