Jackson cannot construct instance with one parameter constructor - java

I am using Spring Boot to create a web application. One of the endpoints expect a json object having one property, i.e. studentId. I am using DTO like my other functions to capture the payload.
#PostMapping("/courses/{id}/students")
public SuccessResponse<Void> addEnrolls(#PathVariable Long id, #RequestBody StudentIdPayload payload) throws HandledException {
courseService.addEnrolls(id, payload.getStudentId());
return success(HttpStatus.OK);
}
#Data
#AllArgsConstructor
public class StudentIdPayload {
private Long studentId;
}
But when I tried to post the endpoint with json body {"studentId":1}, I got the following error :
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `org.bimoadityar.univms.dto.input.StudentIdPayload` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
While it works if I post using just the value 1.
How can I get it to work with the object payload?
Interestingly, when I add another property to the StudentIdPayload, such as String placeholder, it works as intended, although this solution feels hacky.

Considering https://github.com/FasterXML/jackson-databind/issues/1498, it seems that this is the intended behavior.
For my particular case, I am satisfied with adding the #JsonCreator to my constructor.
#Data
#AllArgsConstructor(onConstructor = #__(#JsonCreator))
public class StudentIdPayload {
private Long studentId;
}

By default, deserialization requires no-args constructor, so add #NoArgsConstructor:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class StudentIdPayload {
private Long studentId;
}
see also:
Annotations: using custom constructor

Related

Spring Boot: Problems deserializing JSON objects. Change property names

I'm developing a back-end web application using Spring Boot with Java and I have the following problem:
A REST service returns me the following JSON:
{
"cap":"98888"
}
This is my JAVA class which models the output based on the content:
#NoArgsConstructor
#Data
public class MyObject {
private String cap;
}
I would like to change the field name for MY service to return the following JSON:
{
"CAP":"98888"
}
In my JAVA code, I make the call via RestTemplate, like this:
return restTemplate.postForObject(uriBuilder.build().toUri(), new HttpEntity<>(request, headers), MyObject.class);
I've tried to use tons of stuff with Jackson, including #JsonProperty, like this:
#NoArgsConstructor
#Data
public class MyObject {
#JsonProperty("CAP")
private String cap;
}
But the result is this:
{
"CAP":null
}
As if it no longer matches the original name of the property. In practice I can only get an output with correct value but original field name (WHICH I DON'T WANT) or an output with the name of the field I want (i.e. CAP) but with a null value.
Which is the right way to rename properties with Spring annotations?

I want one of the String member variables in my Spring Boot Java #Entity model class to show up as an actual JSON object when sent to the client side

I want one of the String member variables in my Spring Boot Java #Entity model class to show up as an actual JSON object when sent to the client side, but I am not sure how this is possible
I have this #Entity class in Spring Boot :
#Entity
#Data
public class A {
#JsonProperty
private String value
}
That "value" in the class A is supposed to be a JSON object, but I had to keep it in string type or it won't save into the DB by JPA.
When the controller returns this model object to the client side, the "value" in the JSON string will show up as a full string instead of a JSON struct.
i.e.
This shows up
{
"value": "{\"another_value\":\"1234\"}"
}
instead of
{
"value": {"another_value":"1234"}
}
Is there some easy way to annotate the "value" in my class A to fix this?
Thanks
You have to provide a custom Getter for converting it while de-serializing. I am using google gson for deserializing.
#Entity
#Data
public class A {
#JsonProperty
private String value;
#JsonGetter("value")
public HashMap getValueAsJson(){
return new Gson().fromJson(value, HashMap.class);
}
}

extra data while parsing boolean value in json by using Jackson

When I'm parsing boolean value in JSON by using Jackson, I not only get my expected data, but also an extra key-value data. I want to deserialize the JSON into Java Beans and then serialize it into a String again after processing it. The extra data is in the finally result.Here is my JSON data:
{"is_charging": true}
But I get this after I parse it and then serialize it:
{"is_charging": true, "charging": true}
And here is my Java bean:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
public class Data {
#JsonProperty("is_charging")
public boolean isCharging;
}
However, if I don't use the #JsonProperty, it can not deserialize the "is_charging" and deserialize it as false by default.
How can I solve this? Thanks!
It is the lombok.Getter and lombok.Setter annotations that cause the issue.
public class Data {
#JsonProperty("is_charging")
public boolean isCharging;
}
objectMapper.writeValueAsString(new Data());
Works as expected.
The problem occurs when the #Getter and #Setter annotations are added.
I don't have experience with this lombok library but as far as I understand it creates getter and setter methods for you.
By configuring objectMapper you can disable auto detecting of getter and setter methods so only fields can be serialized and deserialized.
#Getter
#Setter
public class Data {
#JsonProperty("is_charging;")
public boolean isCharging;
}
public static void main(String... args) throws JsonProcessingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
objectMapper.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false);
objectMapper.configure(MapperFeature.AUTO_DETECT_SETTERS, false);
Data data = objectMapper.readValue("{\"is_charging\": true}", Data.class);
System.out.print(objectMapper.writeValueAsString(data));
}
Outputs:
{"is_charging":true}
Note that only objectMapper.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false); is required in your case. Others are provided for reference in case you need them.
It is possible by changing the attribute name from isCharging to charging
#Getter
#Setter
public class Data {
#JsonProperty("is_charging")
public boolean charging;
}
Result:
{"is_charging": true}
AUTO_DETECT_IS_GETTERS is a mapper feature that determines whether "is getter" methods are automatically detected based on standard Bean naming convention or not. If yes, then all public zero-argument methods that start with prefix "is", and whose return type is boolean are considered as "is getters". If disabled, only methods explicitly annotated are considered getters.
By default the feature is enabled. You can disable it while configuring your object mapper. Use,
disable(MapperFeature.AUTO_DETECT_IS_GETTERS);
which is method in ObjectMapper class

XmlElement ignored by Jackson during serialization

i'm using Jersey to build a REST service and as Json Processor i set Jackson in my application.
#javax.ws.rs.ApplicationPath("/")
public class MyApplication extends ResourceConfig {
public MyApplication() {
packages("controller");
register(JacksonFeature.class);
}
I implement a ContextResolver for Jacksons ObjectMapper (as it's suggested in this post Configure Jersey/Jackson to NOT use #XmlElement field annotation for JSON field naming) which creates an ObjectMapper that doesn't fail on unknown properties during deserialization:
#Provider
public class MyJsonObjectMapperProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type)
{
System.out.println("mapper!!!");
ObjectMapper result = new ObjectMapper();
result.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return result;
}
}
and then i register this class in my application inserting register(MyJsonObjectMapperProvider.class) in the class MyApplication shown above. I obtain what i want, in sense that if there are unknown properties in the json the object mapper doesn't fail.
My problem is another; i have this class that i use to map a specified Json, in order to deserialize it and subsequently serialize it:
public class Version {
private String status;
private String updated;
private String id;
private List<Link> links;
#XmlElement(name = "media-types")
private List<MediaTypes> media_types;
//constructor + getter and setter
}
The problem is about the element media_types and the use of the annotation #XmlElement. Before i insert the ContextResolver to personalize ObjectMapper all works fine, in fact after serialization i obtain a json in which the element/attribute media_types has as name media-types; on the contrary with ContextResolver this element doesn't change it's name and has media_types. I think that, during serialization, the annotation XmlElement doesn't work, but i'm not sure that this is the correct reason.
Another attempt i try to do is to put #JsonProperty("media-types") annotation instead of #XmlElement annotation but with no result; in fact with this annotation i obtain also a Processing Exception.
The last attempt (in addition to what has been suggested by the previous post) was that of insert these lines of code in the ContextResolver:
AnnotationIntrospector intr = new AnnotationIntrospector.Pair(new JaxbAnnotationIntrospector(),new JacksonAnnotationIntrospector());
// usually we use same introspector(s) for both serialization and deserialization:
result.getDeserializationConfig().withAnnotationIntrospector(intr);
result.getSerializationConfig().withAnnotationIntrospector(intr);
in order to use both JaxbAnnotation and JacksonAnnotation but the name of the field in question remain media_types.
I hope i was clear in explain my problem and thanks you in advance for your help!

Jackson JSON library: how to instantiate a class with abstract fields that can't access its concrete representation?

This is the same questions than :
Jackson JSON library: how to instantiate a class that contains abstract fields
Nevertheless its solution is not possible since my abstract class is in another project than the concrete one.
Is there a way then ?
EDIT
My architecture is as follows:
public class UserDTO {
...
private LanguageDTO lang;
}
I send that object user :
restTemplate.postForObject(this.getHttpCore().trim() + "admin/user/save/1/" + idUser, userEntity, UserDTO.class);
Then I am supposed to receive it in the function :
#RequestMapping(value = "/save/{admin}/{idUser}", method = RequestMethod.POST)
public String saveUserById(#RequestBody final UserEntity user, #PathVariable Integer idUser, #PathVariable boolean admin)
with UserEntity defined as :
public class UserEntity extends AbstractUserEntity {
...
}
public abstract class AbstractUserEntity {
...
private AbstractLanguageEntity lang;
}
I would like to know how I can specify that lang should be instantiate as LanguageEntity whereas abstract classes are in another project.
This could work assuming you can configure how the object get serialized. See the example here. Look under "1.1. Global default typing" to set the defaults to include extra information in your JSON string, basically the concrete Java type that must be used when deserializing.
Since it seems you need to do this for your Spring servlet, you would have to pass a Spring message converter as mentioned here
Then inside your custom objectMapper, you can do the necessary configuration:
public class JSONMapper extends ObjectMapper {
public JSONMapper() {
this.enableDefaultTyping();
}
}
You could probably also make it work with Mix-ins, which allow you to add annotations to classes already defined. You can see and example here. This will also need to be configured inside the objectMapper.
If you need the same functionality on your client side (REST template), you can pass the object mapper as shown here.
The easiest way to solve that issue is to add getters et setters in UserEntity but specifying a concrete class :
public LanguageEntity getLang() {
return (LanguageEntity) lang;
}
public void setLang(LanguageEntity language){
this.lang = language
}
If all that you want to achieve is to note that LanguageEntity is the implementation of AbstractLanguageEntity, you can register this mapping via module:
SimpleModule myModule = new SimpleModule())
.addAbstractTypeMapping(AbstractLanguageEntity.class,
LanguageEntity.class);
ObjectMapper mapper = new ObjectMapper()
.registerMdoule(myModule);

Categories