Getting the right JavaType TypeFactory - java

I have to deserialize my Java Object and I can't use static reference i.e TypeReference.
Like,
mentioned in here
So, I am left with is generating the right Javatype using type factory, but somehow I am not able to get the syntax right.
Aforementioned are my Classes and Interfaces.
public interface Request {}
public interface Response {}
public class MyRequest implements Request {
int id;
//Getter //Setter
}
public class MyResponse implements Response {
int id;
//Getter //Setter
}
public class UberObject<S extends Request, T extends Response> implements Serializable {
private S request;
private T response;
//Getter//Setter
}
public class UberObjectWithId<S extends Request, T extends Response> extends UberObject<S, T> {
private int id;
//Getter //Setter
}
UberObjectWithId typereferencedObject =
objectmapperobject.readValue(serialisedUberObjectWithId,
new TypeReference<UberObjectWithId<MyRequest, MyResponse>>() {});
The above approach works but I can't use TypeReference because of the limitation mentioned in the above link.
I tried using the Typefactory to retrieve the JavaType but I am not able to get the syntax right.
JavaType type = mapper.getTypeFactory().constructParametrizedType(UberObjectWithId.class,
UberObject.class, Request.class, Response.class);
but the call fails
objectmapperobject.readValue(serialisedUberObjectWithId, type);
How can I resolve my particular problem?

I fixed it by using the correct syntax to generate the JavaType
JavaType type = mapper.getTypeFactory().constructParametrizedType(UberObjectWithId.class,
UberObject.class, MyRequest.class, MyResponse.class);
It requires concrete classes while deserializing.

Related

ModelMapper map List<...> to object property

I am struggling with mapping List into responseDTO.getList()
My code:
MessageDTO
#Getter
#Setter
public Class MessageDTO() {
private String message;
...
}
MessagesDTO
#Getter
#Setter
public Class MessagesDTO() {
private List<> message;
}
MyConverter
public class MyConverter extends AbstractConverter<List<MessageDTO>, MessagesDTO> {
#Override
protected ChatMessagesResponseDTO convert(List<MessageDTO> source) {
MessagesDTO destination = new MessagesDTO();
destination.setMessages(source);
return destination;
}
}
Controller
...
List<MessageDTO> messages = ... // result of service and succesfull mapping entity to dto
ModelMapper mm = new ModelMapper();
Converter conv = new MyConverter();
mm.addConverter(conv);
MessagesDTO messagesDTO = mm.map(messages, MessagesDTO.class)
return messagesDTO; // always null
Any ideas why it is not working ? I am sucessfuly using modelmapper in many other places of my project even with custom TypeMap(s) and Converter(s), but cannot find a way how to map list of some type into DTO attribute which is list of that type.
This is because of type erasure. ModelMapper is unable to recognize the generic type of a List and thus does not apply your converter. I'm not sure if it is possible to achieve with classes you presented but if it is it might be quite complicated task.
One solution would be to declare class that has the type stored runtime. So like:
#SuppressWarnings("serial")
public static class MessageDTOList extends ArrayList<MessageDTO> {};
and make required changes to your converter, so to be:
public class MyConverter extends AbstractConverter<MessageDTOList, MessagesDTO> {
#Override
protected MessagesDTO convert(MessageDTOList source) {
MessagesDTO destination = new MessagesDTO();
destination.setMessages(source);
return destination;
}
}
If it is hard to get the response directly as a MessageDTOList you can always:
List<MessageDTO> messages = ... // result of service and succesfull mapping entity
MessageDTOList messagesDerived = new MessageDTOList();
messagesDerived.addAll(messages);
and then just:
MessagesDTO messagesDTO = mm.map(messagesDerived, MessagesDTO.class);

Generics of SuperClass

I'm trying to using generics with the reactive webclient, so far I've create a class that holds the webClient and some methods
public class CustomWebClient<T> {
private final WebClient webClient;
public CustomWebClient(WebClient.Builder builder, ClientProperties properties) {
String rootUrl = properties.getHttp().getRootUrl();
webClient = builder
.baseUrl(rootUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeaders(httpHeaders -> httpHeaders.setAll(properties.getHeaders()))
.build();
}
public ​Mono<ResponseEntity<T>> setRequest(final String uri,
​final HttpMethod httpMethod,
​final Class<T> classToMap) {
WebClient.RequestHeadersSpec<?> requestHeadersSpec = webClient.method(httpMethod).uri(uri);
​WebClient.ResponseSpec responseSpec = setRetrievingOptions(requestHeadersSpec);
​return setEntity(responseSpec, classToMap);
}
private WebClient.ResponseSpec setRetrievingOptions(final WebClient.RequestHeadersSpec<?> requestHeadersSpec) {
return requestHeadersSpec.retrieve()
.onStatus(HttpStatus::is4xxClientError,
clientResponse -> Mono.error(new ResponseStatusException(clientResponse.statusCode(), NOT_FOUND_MSG)))
.onStatus(HttpStatus::is5xxServerError,
clientResponse -> Mono.error(new ResponseStatusException(clientResponse.statusCode(), INTERNAL_SERVER_ERROR_MSG)));
}
private Mono<ResponseEntity<T>> setEntity(final WebClient.ResponseSpec responseSpec, Class<T> classToMap) {
return responseSpec.toEntity(classToMap);
}
}
then I inject the CustomWebClient into another class which is a service
public class ABCServiceImpl implements ABCService {
private final CustomWebClient<? extends BaseResponse> customClient;
private Mono<PageResponse> invokeGetPageFromABC(final String storeId, final String site, final MultiValueMap<String, String> queryParams) {
Mono<ResponseEntity<PageResult>> responseEntityMono = customClient.setRequest(String.format(PAGE_ENDPOINT, site), HttpMethod.GET, queryParams, PageResult.class);
return responseEntityMono.map(content ->
new ContentResponse(content.getStatusCode(), customWebClient.SUCCESS_MSG, createContent(content.getBody())));
}
private ContentDTO createContent(ContentResult contentResult){
return Optional.ofNullable(contentResult)
.filter(c -> Objects.nonNull(c.getContent()))
.map(ContentResult::getContent)
.orElse(new ContentDTO());
}
}
at this point a get a compile time error which is
Required type: Class <capture of ? extends BaseResponse>
Provided: Class <ContentResult>
Here is the object I'm passing
public class ContentResult extends BaseResponse {
private ContentDTO content;
}
Since I'm new with generics probably there is something I'm missing or I didn't fully understand. Any clue about what could be the problem?
Honestly I've expected to work given the fact that I've actually specified the super class while injecting the customWebClient.
Thank you in advance!
I've combed through this, and I think I understand what you're trying to do. I recommend not using the wildcard ("?") in this way. When you call customClient.setRequest(..), the compiler can't figure out what version of customClient you have (i.e. what subclass of BaseResponse), so it throws an error when you try to pass in the parameterized type Class object (which looks to be PageResult.class)
You could fix this by turning ABCServiceImpl into a parameterized class i.e.
ABCServiceImpl<T extends BaseResponse> with a field property of private final CustomWebClient<T extends BaseResponse> customClient;
But I still wouldn't do that either, because it looks like your custom client does not need to be a parameterized type at all!
So I'd recommend this:
Remove the parameterized type from CustomWebClient. (i.e. public class CustomWebClient instead of public class CustomWebClient<T>)
Make the public ​Mono<ResponseEntity<T>> setRequest(..) method a parameterized method instead: public <T> ​Mono<ResponseEntity<T>> setRequest(..)
Call the method on customWebClient like so:
customWebClient.<ContentResult>setRequest(ContentResult.class, ...etc);

Custom Deserializer to map a String to a boolean value

I am making an external call via RestTemplate as follows:
ResponseEntity<Response> response = template.exchange("some.endpoint.com", HttpMethod.POST, request, MyClass.class);
The API returns a boolean value in String format as follows: ("0" or "1")
{
"some_lengthy_key_name" : "1"
}
I am trying to map this response to the following class.
#Getter
#JsonDeserialize(builder = MyClass.MyClassBuilder.class)
#Builder
public class MyClass{
#JsonProperty("some_lengthy_key_name")
private final boolean isValid;
}
It looks like Jackson doesn't entertain this and throws following error (understandable):
Can not deserialize value of type boolean from String "1" only "true"
or "false" recognized.
I don't want to go down the route of capturing it as a String and then modifying value after.
Instead prefer to go for the option of getting a custom deserialization going and went for the following:
public class Deserializer extends JsonDeserializer<Boolean> {
#Override
public Boolean deserialize(JsonParser parser, DeserializationContext context) throws IOException {
return !"0".equals(parser.getText());
}
}
I've now annotated the field in MyClass as follows:
#Getter
#JsonDeserialize(builder = MyClass.MyClassBuilder.class)
#Builder
public class MyClass{
#JsonDeserialize(using = Deserializer.class)
#JsonProperty("some_lengthy_key_name")
private final boolean isValid
}
But unfortunately this is not working either and throwing same error.
Could I get some advice as to what I am doing wrong with this custom deserialization? Thanks.
You don't need any custom deserializer for this, just override the property with custom setter method and then annotated that method with #JsonProperty. Another note jackson uses getters and setters for serialization and deserialization so you cannot declare variables as final
#Getter
#Setter
class MyClass{
private boolean isValid;
#JsonProperty("some_lengthy_key_name")
public void setIsValid(String value) {
isValid = "1".equals(value);
}
}
There is another way in jackson for deserializing using constructor to prevent immutability of object, check this for more information
#Getter
#Setter
#JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public class MyClass{
private final boolean isValid;
public MyClass(#JsonProperty("some_lengthy_key_name") String name) {
this.isValid = "1".equals(name);
}
You can not use final, and use a Boolean type instead of boolean
#Getter
#Setter
public class MyClass{
#JsonDeserialize(using = Deserializer.class)
#JsonProperty("some_lengthy_key_name")
private Boolean isValid; //<= You did not use final, and use Boolean type instead of boolean
}

ObjectMapper deserializing set all fields = NULL

I have superclass:
#Getter
#Setter
public abstract class AbstractDto {
#NotNull
protected String num;
}
And subclass:
#Getter
#Setter
public class SomeDto extends AbstractDto {
#NotNull
private Long id;
}
When I serialize it, all is ok:
{"num":"5600511164","id":22}
But when I try to deserialize, I get all fields as null in SomeDto.
I use this configuration
#Bean
public ObjectMapper objectMapper (Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}
I think problem is in use superclass, but I don't know how to fix it. Without superclass always work fine.
UPD.
Serialize like this:
protected String marshall(Object message) throws JsonProcessingException {
return this.objectMapper.writeValueAsString(message);
}
Deserialize like this:
protected T unmarshall(byte[] body) throws IOException {
return objectMapper.readValue(body, resolveType());
}
resolveType() return me SomeDto.class
UPD. Solved.
Problem was in body, it's not only {"num":"5600511164","id":22}, it contains another info.

Infinite recursion when serializing OrientDB RecordID

In my schema I have an abstract class like this:
#JsonAutoDetect(JsonMethod.NONE)
public abstract class AbstractEntity {
#Id private Object id;
#Version private Integer version;
public AbstractEntity() {}
#JsonProperty // this annotation causes infinite recursion
public Object getId() { return id; }
}
This class is used as superclass for every entity, for example:
#JsonAutoDetect(JsonMethod.NONE)
public class Usuario extends AbstractEntity {
private Cadastro cadastro;
protected Usuario() {}
public Usuario(Cadastro cadastro) {
setCadastro(cadastro);
}
#JsonProperty
public Cadastro getCadastro() { return cadastro; }
#JsonProperty
public void setCadastro(Cadastro cadastro) { this.cadastro = cadastro; }
}
I'm designing a REST layer which will let users create records in db via JSON request/response:
#POST
#Path("cadastrar")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response cadastrar(#Context HttpServletRequest request, Cadastro cadastro) {
OObjectDatabaseTx bd = (OObjectDatabaseTx) request.getAttribute("object.database.tx");
try {
bd.begin();
Usuario usuario = new Usuario(cadastro);
usuario = bd.save(usuario);
bd.commit();
String json = new ObjectMapper().writeValueAsString(usuario);
Response response = Response.status(HttpURLConnection.HTTP_CREATED).entity(json).build();
return response;
(...)
However, in this last method, when I call Jackson to serialize my newly created entity (in writeValueAsString method), I get an infinite recursion:
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain:
com.orientechnologies.orient.core.id.ORecordId["record"]->com.orientechnologies.orient.core.record.impl.ODocument["identity"]
->com.orientechnologies.orient.core.id.ORecordId["record"]->com.orientechnologies.orient.core.record.impl.ODocument["identity"]
->com.orientechnologies.orient.core.id.ORecordId["record"]->com.orientechnologies.orient.core.record.impl.ODoc...
I can avoid the infinite recursion by removing #JsonProperty from getId method in AbstractEntity class. However, this way the id property won't be present in JSON response.
So, how can I serialize the id property?
Thanks!
Does Jackson support circular references? Is that the case?

Categories

Resources