I have a parent wrapper class that I need to use to transport data between our systems in a unified format, and I need to be able to deduce the sub-type of it. I was able to make this work with a custom deserializer, but I've been tasked with re-implementing it based on annotations. I've gotten partway there, but I'm hitting an InvalidDefinitionException now that I think stems from Jackson looking for a 1-String constructor to my inner type, instead of using the #JsonCreator all-args constructor that used to work before with my Deserializer.
There should be a way to have the TypeInfo and SubTypes cascade down the hierarchy, even with a wrapper/holder type, right?
Wrapper Classes:
#JsonTypeInfo(use=JsonTypeInfo.NAME, property="type", visible=true)
#JsonSubTypes({
#JsonSubTypes.Type(value=UserApiMessage.class, name=UserPayloadNameEnum.Constant.LOGIN),
#JsonSubTypes.Type(value=UserApiMessage.class, name=UserPayloadNameEnum.Constant.CONFIG_REQUEST),
#JsonSubTypes.Type(value=SystemApiMessage.class, name=SystemPayloadNameEnum.Constant.METRICS_REQUEST),
#JsonSubTypes.Type(value=SystemApiMessage.class, name=SystemPayloadNameEnum.Constant.METRICS_RESPONSE),
...
})
public abstract class ApiMessage {
public abstract String getRequestId();
public abstract Enum getType(); //Object mapper de/serializes on Enum.name
public abstract ApiPayload getPayload();
}
public class UserApiMessage extends ApiMessage {
private final String requestId;
private final UserPayloadNameEnum type;
#JsonTypeInfo(use=JsonTypeInfo.NAME, include=JsonTypeInfo.As.EXTERNAL_PROPERTY, property="type", visible=true)
#JsonSubTypes({
#JsonSubTypes.Type(value=Login.class, name=UserPayloadNameEnum .Constant.LOGIN),
#JsonSubTypes.Type(value=ConfigRequest.class, name=UserPayloadNameEnum.Constant.CONFIG_REQUEST)
})
private final UserPayload payload;
//All-Args #JsonCreator constructor
//All #Override getters
}
public class SystemApiMessage extends ApiMessage {
private final String requestId;
private final SystemPayloadNameEnum type;
#JsonTypeInfo(use=JsonTypeInfo.NAME, include=JsonTypeInfo.As.EXTERNAL_PROPERTY, property="type", visible=true)
#JsonSubTypes({
#JsonSubTypes.Type(value=MetricsRequest.class, name=SystemPayloadNameEnum .Constant.METRICS_REQUEST),
#JsonSubTypes.Type(value=MetricsResponse.class, name=SystemPayloadNameEnum .Constant.METRICS_RESPONSE)
})
private final SystemPayload payload;
//All-Args #JsonCreator constructor
//All #Override getters
}
A parent ApiPayload class, which is polymorphic
public abstract class ApiPayload {
...
}
public abstract class UserPayload extends ApiPayload {
...
}
public abstract class SystemPayload extends ApiPayload {
...
}
And their concrete implementations.
public class Login extends UserPayload {
//All-Args #JsonCreator as constructor
}
public class ConfigRequest extends UserPayload {
//All-Args #JsonCreator as constructor
}
public class MetricsRequest extends SystemPayload {
//All-Args #JsonCreator as constructor
}
public class MetricsResponse extends SystemPayload {
//All-Args #JsonCreator as constructor
}
When executing mapper.readValue(json, ApiMessage.class); on a well-formed "Login" request that worked with my old custom deserializer, I get the following error.
InvalidDefinitionException: Cannot construct instance of 'com.foo.bar...UserApiMessage', problem: argument type mismatch.
at(Source: (String)" {
My well-formed json
}"; line 15, column: 1]
So if I'm reading this correctly, Jackson is looking for a 1-string constructor of UserApiMessage, rather than continuing the type deduction. What am I doing wrong?
Side Note: if I add the #JsonTypeInfo with WRAPPER_OBJECT and repeat the SubTypes on the class signature for UserPayload, I actually get to the point it tries to construct a Login object, but then all of the args are missing (given default/null values).
Related
According to this post ResourceAssembler is changed to RepresentationModelAssembler
I have this code which is using Spring HATEOAS 1.0:
import org.springframework.hateoas.ResourceAssembler;
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements ResourceAssembler<T, D> {
...
}
After migration to implementation 'org.springframework.boot:spring-boot-starter-hateoas:2.6.4'
I changed it to:
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements RepresentationModelAssembler<T, D> {
.........
}
But I get error:
Type parameter 'D' is not within its bound; should extend 'org.springframework.hateoas.RepresentationModel<?>'
Do you know how I can fix this issue?
The compiler is reporting that the type parameter D is not within its bound in your definition:
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements RepresentationModelAssembler<T, D> {
.........
}
In other words, it means that you cannot use D extends BaseResource to implement RepresentationModelAssembler<T, D> (note the type parameter D here) because that type should extend 'org.springframework.hateoas.RepresentationModel<?>'.
RepresentationModelAssembler gives you the ability to convert between domain types, your entities, to RepresentationModels, a based class conceived to enrich your DTOs to collect links.
It is defined as follows:
public interface RepresentationModelAssembler<T, D extends RepresentationModel<?>>
Note again the definition of the type parameter D.
In your code you need to use something like:
public class BaseAssembler<T extends BaseTransaction, D extends RepresentationModel<?>>
implements RepresentationModelAssembler<T, D> {
.........
}
Please, consider read for instance some this or this other article, they provide a great variety of examples and uses cases about showcasing how you can implement the desired behavior.
For example, given the following entity, extracted from one of the cited articles:
#Entity
public class Director {
#Id
#GeneratedValue
#Getter
private Long id;
#Getter
private String firstname;
#Getter
private String lastname;
#Getter
private int year;
#OneToMany(mappedBy = "director")
private Set<Movie> movies;
}
And the following DTO:
#Builder
#Getter
#EqualsAndHashCode(callSuper = false)
#Relation(itemRelation = "director", collectionRelation = "directors")
public class DirectorRepresentation extends RepresentationModel<DirectorRepresentation> {
private final String id;
private final String firstname;
private final String lastname;
private final int year;
}
Your RepresentationModelAssembler will look like:
#Component
public class DirectorRepresentationAssembler implements RepresentationModelAssembler<Director, DirectorRepresentation> {
#Override
public DirectorRepresentation toModel(Director entity) {
DirectorRepresentation directorRepresentation = DirectorRepresentation.builder()
.id(entity.getId())
.firstname(entity.getFirstname())
.lastname(entity.getLastname())
.year(entity.getYear())
.build();
directorRepresentation.add(linkTo(methodOn(DirectorController.class).getDirectorById(directorRepresentation.getId())).withSelfRel());
directorRepresentation.add(linkTo(methodOn(DirectorController.class).getDirectorMovies(directorRepresentation.getId())).withRel("directorMovies"));
return directorRepresentation;
}
#Override
public CollectionModel<DirectorRepresentation> toCollectionModel(Iterable<? extends Director> entities) {
CollectionModel<DirectorRepresentation> directorRepresentations = RepresentationModelAssembler.super.toCollectionModel(entities);
directorRepresentations.add(linkTo(methodOn(DirectorController.class).getAllDirectors()).withSelfRel());
return directorRepresentations;
}
}
In terms of your interfaces and object model:
#Entity
public class Director extends BaseTransaction{
#Id
#GeneratedValue
#Getter
private Long id;
#Getter
private String firstname;
#Getter
private String lastname;
#Getter
private int year;
#OneToMany(mappedBy = "director")
private Set<Movie> movies;
}
public class DirectorRepresentationAssembler
extends BaseAssembler<Director, DirectorRepresentation>
implements RepresentationModelAssembler<Director, DirectorRepresentation> {
//... the above code
}
DirectorRepresentation is the same as presented above.
The Spring HATEOAS reference guide itself provides some guidance as well about the changes performed in Spring HATEOAS 1.0 and about how to migrate from the previous version. It even includes a script that may be of help.
In any case, as indicated above, in your use case you only need to modify the BaseAssembler interface to be defined in terms of the type D extends RepresentationModel<?>; then try relating in some way BaseResource to RepresentationModel or get rid of BaseResources and use RepresentationModels instead.
For example, you couild try defining BaseResource as follows:
public class BaseResource extends RepresentationModel<BaseResource>{
// your implementation
}
Then, the bound will be right:
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements RepresentationModelAssembler<T, D> {
// your implementation
}
With these changes, DirectorRepresentation will extend BaseResource:
public class DirectorRepresentation extends BaseResource {
}
And you can extend BaseAssembler like this:
public class DirectorRepresentationAssembler
extends BaseAssembler<Director, DirectorRepresentation>
implements RepresentationModelAssembler<Director, DirectorRepresentation> {
// your implementation
}
In my opinion, the code you published in your repository is mostly fine. I think the only problem is in this line of code, as I mentioned before, I think you need to provide the type parameter when defining your BaseResource class. For instance:
package com.hateos.test.entity.web.rest.resource;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.joda.time.DateTime;
import org.springframework.hateoas.RepresentationModel;
import java.util.UUID;
public class BaseResource extends RepresentationModel<BaseResource> {
#JsonProperty
#ApiModelProperty(position = 1, required = true)
public UUID id;
#JsonProperty
public DateTime creationTime;
#JsonProperty
public DateTime lastUpdatedTime;
}
Please, note the inclusion of the code fragment RepresentationModel<BaseResource> after the extends keyword.
I am not sure if it will work but at least with this change every compiles fine and it seems to work properly.
I have two classes
public class Account {
#ApiModelProperty(example = "EUR")
private CurrencyCode currency;
....// public setters and getters
}
and
public class SpecificAccount extends Account {
//Here I need to override the currencie example without redefining the currency field
#ApiModelProperty(example = "USD")
private CurrencyCode currency;
}
Can we override swagger discerption/example using #ApiModelProperty in inherited class ?
You should add #ApiModel annotations, also classes should have getters for all fields you want to have documented.
If you have only getters, then args constructor is required, in case you have getters and setters then default one is enough.
#ApiModel(subTypes = {SpecificAccount.class})
public class Account {...}
#ApiModel(parent = Account.class)
public class SpecificAccount extends Account {...}
I am quite new to Java and I am trying to deserialize the JSON using Jackson and I facing some minor issue with regards to declaring the Object/Variable type. I will provide all the codes then explain the issue for easy understanding.
I have an enum that will have the required type values:
public enum IdentifierTypeValues {
Type1,
Type2,
Type3,
//Constructor and Getter of enum values
}
Then for each of these type, I have different classes which will have different input and do a completely different type of process:
public class GenerateType1 {
private String name;
private String age;
//Getter and Setter
//Some required process based on these values
}
public class GenerateType2 {
private String address;
private String city;
private String country;
//Getter and Setter
//Some required process based on these values
}
public class GenerateType3 {
private String firstName;
private String lastName;
private String fullName;
//Getter and Setter
//Some required process based on these values
}
Now I have a wrapper class for these type of classes which will take the type based on enum and typeInfo values. I want the typeInfo values to be any of the class based type something like this:
public class TypeSyntax {
private IdentifierTypeValues indeitiferType;
private GenerateType1 / GenerateType2 / GenerateType3 identifierTypeValues;
//Here the identifierTypeValues can have the values for anytype
//How to declare a variable of any of these class type?
}
This is the class that will be used by my JSON for deserializing. I know I can add a wrapper class of those 3 types and provide that wrapper class as a type class for this. Something like this:
public class WrapperClass{
private GenerateType1 type1;
private GenerateType2 type2;
private GenerateType3 type3;
}
public class TypeSyntax{
private IdentifierTypeValues indeitiferType;
private WrapperClass identifierTypeValues;
//But using this approach will change my JSON structure which I do not want to do.
}
My JSON structure is something like this and I would like to keep it in the same way.
{
"indeitiferType":"Type1",
"identifierTypeValues":{
"name":"Batman",
"age":"2008"
}
}
Is there a way I can declare the variable of multiple type class? or any better approach to handle this by keeping the json format same? I tried searching but I am unable to search what exactly so any help would be really appriciated.
Because the type identifier exists on a different level than the other properties a wrapper class TypeSyntax needed. There are several open feature requests to add wrapping functionality to Jackson e.g. https://github.com/FasterXML/jackson-databind/issues/512
Fortunately polymorphism is supported in Jackson with #JsonTypeInfo and #JsonSubTypes annotations.
Wrapper class should look like:
public class TypeSyntax {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "identifierType")
private GenerateTypeBase identifierTypeValues;
// getters and setters (omitted for brevity)
}
GenerateTypeBase is the common parent class
#JsonSubTypes({
#JsonSubTypes.Type(value = GenerateType1.class, name = "Type1"),
#JsonSubTypes.Type(value = GenerateType2.class, name = "Type2"),
})
public abstract class GenerateTypeBase {
private String name;
private String age;
// getters and setters (omitted for brevity)
}
In this different children classes will instantiated based on the identifierType property.
The children must extend this base class:
public class GenerateType2 extends GenerateTypeBase {
// additional properties
}
In a short test it will be:
#Test
void wrapperTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
GenerateType2 a = new GenerateType2();
a.setName("Foo");
a.setAge("13");
TypeSyntax w = new TypeSyntax();
w.setIdentifierTypeValues(a);
String json = mapper.writeValueAsString(w);
System.out.println(json);
}
and the output:
{
"identifierTypeValues":
{
"name":"Foo",
"age":"13"
},
"identifierType":"Type2"
}
Deserialization
#Test
void wrapperTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
String input = "{\"identifierTypeValues\": \"name\":\"Foo\",\"age\":\"13\"},\"identifierType\":\"Type2\"}";
TypeSyntax w = mapper.readValue(new StringReader(input), TypeSyntax.class);
assertAll(
() -> assertEquals(GenerateType2.class, o.getIdentifierTypeValues().getClass()),
() -> assertEquals("13", o.getIdentifierTypeValues().getAge())
);
}
If you want more flexibility you can write custom (de)serializer and / or custom resolver. Using custom TypeIdResolver that will possible to convert identifiers to types programmatically instead of using "key-value pairs" in #JsonSubTypes
Is there any class mapping framework which works with builders? I would like to keep some of my classes immutable and avoid multiple constructors - the Builder Pattern comes to the rescue. However I can't any mapping framework which would use builder automatically instead of getters/setters.
I got the following working with Lombok and ModelMapper. See: http://modelmapper.org/getting-started/
public class MyService {
private ModelMapper modelMapper;
public MyService(){
this.modelMapper = new ModelMapper();
this.modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setDestinationNamingConvention(LombokBuilderNamingConvention.INSTANCE)
.setDestinationNameTransformer(LombokBuilderNameTransformer.INSTANCE);
}
public OutputDTO aMethod(final InputDTO input){
return modelMapper.map(input, OutputDTO.OutputDTOBuilder.class).build();
}
}
Where LombokBuilderNamingConvention is:
import org.modelmapper.spi.NamingConvention;
import org.modelmapper.spi.PropertyType;
public class LombokBuilderNamingConvention implements NamingConvention {
public static LombokBuilderNamingConvention INSTANCE = new LombokBuilderNamingConvention();
#Override
public boolean applies(String propertyName, PropertyType propertyType) {
return PropertyType.METHOD.equals(propertyType);
}
#Override
public String toString() {
return "Lombok #Builder Naming Convention";
}
}
And LombokBuilderNameTransformer is:
import org.modelmapper.spi.NameTransformer;
import org.modelmapper.spi.NameableType;
public class LombokBuilderNameTransformer implements NameTransformer {
public static final NameTransformer INSTANCE = new LombokBuilderNameTransformer();
#Override
public String transform(final String name, final NameableType nameableType) {
return Strings.decapitalize(name);
}
#Override
public String toString() {
return "Lombok #Builder Mutator";
}
}
And OutputDTO can look like:
#Builder // Has .builder() static method
#Value // Thus immutable
public class OutputDTO {
private String foo;
private int bar;
}
This can be easily done with MapStruct and using a custom naming strategy for builders.
Have a look here in the documentation how to use Custom Accessor naming strategy.
Your mappings then need to look like:
#Mapper
public interface MyMapper {
default Immutable map(Source source) {
return mapToBuilder(source).build();
}
Immutable.Builder mapToBuilder(Source source);
}
Within MapStruct we are already working on a feature that would support out of the box support for builders. You can follow this issue for more details.
Update
MapStruct now (since 1.3.0.Beta1) has out of the box support for Immutables. This means that the mapper before can be written like:
#Mapper
public interface MyMapper {
Immutable map(Source source);
}
The assumption is that there is a public static method without parameters in Immutable that returns the builder
Uing Lombok and ModelMapper configure as:
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(AccessLevel.PRIVATE);
By default ModelMapper uses only public setter method to map. When the class annotated with Lombok builder annotation it made the setter method as private. So to allow the ModelMapper to use the private setter method we need to add the above configureation.
OR
Configuration builderConfiguration = modelMapper.getConfiguration().copy()
.setDestinationNameTransformer(NameTransformers.builder())
.setDestinationNamingConvention(NamingConventions.builder());
modelMapper.createTypeMap(MyEntity.class, MyDto.MyDtoBuilder.class, builderConfiguration);
where MyEnity class is:
#Data
private static class MyEntity {
private Long id;
private String name;
private String value;
}
and builder class is:
#Data
#Builder
private static class MyDto {
private final Long id;
private final String name;
private final String value;
}
click here for detail
Saying I have an interface A, I want to use custom deserializer for all classes implement interface A, So I use code below but it doesn't work, While CustomAserializer works.
So what should I do to deserialize all classes implement A using my custom deserializer.
Thanks.
module.addDeserializer(A.class, new CustomADeserializer());
module.addSerializer(A.class, new CustomASerializer())
It seems you forgot to annotate your implementation classes with #JsonDeserialize(using = ImplementationClazz.class) to indicate that the class should be used to deserialize the abstract class or interface.
The following is a simple example to deserialize an interface having multiple implementations using Jackson.
Here is my interface:
#JsonDeserialize(using = UserDeserializer.class)
public interface User {
}
One implementation of the interface:
#JsonDeserialize(as = ServiceUser.class)
public class ServiceUser implements User{
private String name;
private String role;
//constructor, setters & getters
Second implementation:
#JsonDeserialize(as = AdminUser.class)
public class AdminUser implements User {
private String role;
private String designation;
//constructor, setters & getters
And here is the deserializer:
public class UserDeserializer extends JsonDeserializer<User> {
#Override
public User deserialize(JsonParser jp, DeserializationContext context) throws IOException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = mapper.readTree(jp);
/*write your own condition*/
if (root.has("name") && root.get("name").asText().equals("XYZ")) {
return mapper.readValue(root.toString(), ServiceUser.class);
}
return mapper.readValue(root.toString(), AdminUser.class);
}
}
You may get a StackOverflowError if you don't annotate the implementation classes. All implementation classes should deserialize themselves, otherwise it will use the deserializer from the parent class which leads to a StackOverflowError.
Just in case someone need a solution to serialize and desiralize inheritance hierarchy
you can use jackson annotation in more elegant way : JsonTypeInfo and JsonSubTypes
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#Type(value = ServiceUser.class, name = "service"),
#Type(value = AdminUser.class, name = "admin")
})
public interface User{
// ...
}