ObjectMapper deserializing set all fields = NULL - java

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.

Related

Jackson custom deserializer causes data to be wiped out if null returned

I have an issue when returning a null from a custom deserializer, it looks like the rest of the json will not be deserialized after.
Unfortunately I can't provide a real code, but here is a short version with all the annotations included.
What would happen if I directly return null: "Problem" will be instantiated, but both fields within the instance will be null. I thought whether #JsonUnwrapped could collide somehow with my custom deserealizer, but even if I remove an annotation, behavior maintains.
But if I consume part of the Json that represents "WannaBeNull", everything works fine, as shown in the last implemetation of the class. Using SpringBoot 2.6.6, Jackson 2.13. Would appreciate any idea about what's going on here.
A.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class A extends B {
private C c;
// basic setter for C, no annotations
}
B.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class B {
#JsonUnwrapped
private Problem problem; // a simple class with 2 fields, one of type Integer and another is String
// basic setter and a getter for Problem, no annotations
...
}
C.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class C extends D {
// nothing important here besides couple of basic fields
}
D.java
public abstract class D {
#JsonDeserialize(using = WannaBeNullCustomDeserializer.class)
private WannaBeNull wannaBeNull;
// basic setter and getter, no annotation
}
WannaBeNullCustomDeserializer.java - NOT WORKING
public class WannaBeNullCustomDeserializer extends StdDeserializer<WannaBeNull> {
public WannaBeNullCustomDeserializer (Class<?> vc) {
super(vc);
}
public WannaBeNullCustomDeserializer () {
this(null);
}
#Override
public EpOrderForVcDto deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return getNullValue(ctxt);
}
}
WannaBeNullCustomDeserializer.java - WORKING
public class WannaBeNullCustomDeserializer extends StdDeserializer<WannaBeNull> {
public WannaBeNullCustomDeserializer (Class<?> vc) {
super(vc);
}
public WannaBeNullCustomDeserializer () {
this(null);
}
#Override
public EpOrderForVcDto deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
new ObjectMapper().readValue(p, WannaBeNull.class);
return getNullValue(ctxt);
}
}
UPDATE
It looks like when inputstream (our json) is being processed, if the json that does represent that object will not be consumed and a null will be returned from a deserializer, it would mess up with tokens/further processing of the entire json, however it does not cause any kind of exception/error.

How can I set an optional RequestBody field without it being deleted when I make the call?

I have a small program in spring-boot which through a get call having a #RequestBody returns me a message with all the specifications (in my case of cars)
public class CarsRequest implements Serializable {
private String name;
private String plate ;
private String price;
}
I would like to be able to make sure that if a field is set to null, it can still find the relative message with the other fields having a value, in my case, I wanted to put that the "name" field is optional in the RequestBody, is it possible to do this? I tried setting
public CarsResponse getCars(#RequestBody (required = false) CarsRequest request) throws IOException {
//some code
}
but then when I go to do the get it completely deletes the null field at the time of the get and therefore fails to do it
Just remove the #RequestBody annotation from the function and keep it as it is
public CarsResponse getCars(CarsRequest request) throws IOException {
//some code
}
Now all fields will be converted into query params and all will be optional, because query param by convention are optional
public class CarsRequest implements Serializable {
private String name;
private String plate ;
private String price;
}
And call like this
GET /someEndpoint?name=<value>&plate=null
But still if you want to make some params mandatory, then use javax.annotations or apply validation yourself.
EDIT: As asked in comment, if you are accepting JSON as parameter body then you can do one thing, you can accept it as String and then convert json to object inside function body
public CarsResponse getCars(#RequestParam(required = false) String request) throws IOException {
ObjectMapper mapper = new ObjectMapper();
CarRequest request = mapper.readValue(request,CarRequest.class);
// other code
}
and call it something like this
GET /someEndpoint?request="{ \"name\" : null, \"plate\": \"someValue\" }"
EDIT 2:
You can do one more thing if you want to keep sending json and have it transformed into object, you can declare a binder something like this
// Some controller class
class SomeController {
#Autowired
ObjectMapper mapper;
// Ommited methods here
#GetMapping("/carRequest")
public ResponseEntity<String> testBinder(#RequestParam CarRequest request) {
return ResponseEntity.ok("{\"success\": \"" + request.name+ "\"}");
}
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(CarRequest.class, new CarRequestEditor(mapper));
}
static class CarRequestEditor extends PropertyEditorSupport {
private ObjectMapper objectMapper;
public CarRequestEditor(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public void setAsText(String text) throws IllegalArgumentException
{
if (StringUtils.isEmpty(text)) {
setValue(new CarRequest());
} else {
try {
setValue(objectMapper.readValue(text, CarRequest.class));
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
}
}
}
Please note that the client need to send the json URL encoded like this
http://localhost:8180/carRequest?request=%7B%22name%22%3"test"%7D
Hi you are using #RequestBody (required = false) CarsRequest
that means your CarsRequest object itself is optional
rather than you can use
#NotEmpty
private String plate ;
#NotEmpty
private String price;
You can make a single field optional by making it an Optional, in your case Optional<String>. If the field does not appear in the request body, then the Optional will be empty.
public class CarsRequest implements Serializable {
private String name;
private String plate;
private Optional<String> price;
}

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
}

Jackson: need a another method to be a setter

public class UrlTemplate extends AbstractBeanDefinition implements Serializable {
private List<HeaderField> header = new ArrayList<>();
#Tag("header")
public List<HeaderField> getHeader() {
return this.header;
}
#Node("header/header-field")
public void setHeader(HeaderField headerField) {
this.header.add(headerField);
}
public void setHeader(List<HeaderField> header) {
this.header = header;
}
}
Tag and Node annotation are used by other library, and I can't change method setHeader(HeaderField headerField).
I define a valid setter setHeader(List<HeaderField> header) for serializing, but when I try to serialize UrlTemplate to String and deserialize String to UrlTemplate, throw exception:
com.fasterxml.jackson.databind.JsonMappingException:
Conflicting setter definitions for property "header":
com.enniu.crawler.encreeper.core.domain.config.search.UrlTemplate#setHeader(1 params) vs
com.enniu.crawler.encreeper.core.domain.config.search.UrlTemplate#setHeader(1 params)
I think I may declare setHeader(List<HeaderField> header) to be the setter that Jackson take to deserialize, but I have no idea about how to do it with the Jackson.
Could any one give me some hint?
Try using JsonProperty annotation from jackson
#JsonProperty("header")
private Object headerJson;
public Object getHeaderJson() {
return status;
}
public void setHeaderJson(Object headerJson) {
this.headerJson= headerJson;
}
headerJson will be mapped to json header so it should resolve the conflict issue.

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