extra data while parsing boolean value in json by using Jackson - java

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

Related

Inform Jackson to throw exception if Json does not match expected object structure

Using this code to convert convert Json to a Java object using Jackson annotations :
import com.fasterxml.jackson.databind.ObjectMapper;
import objectmappertest.Request;
import java.io.IOException;
public static void main(String args[]) throws IOException {
final String json = "{\"datePurchased\":\"2022-02-03 21:32:017\"},{\"unknownField\":\"test\"}";
final Request request = mapper.readValue(json, Request.class);
System.out.println("request : "+request);
}
I expect an exception to be thrown as the Request Java object does not contain a field type unknownField , instead it seems that Jackson parses what it can from the JSON. Is there a configuration option which a causes an exception or a flag to be set if the Json being passed to Jackson does not match the Java object structure ?
Here is the expected structure :
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.jackson.Jacksonized;
import java.util.Date;
#Builder
#ToString
#Getter
#Setter
#Jacksonized
public class Request
{
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:sss")
private final Date datePurchased;
}
FAIL_ON_UNKNOWN_PROPERTIES
Feature that determines whether encountering of unknown properties
(ones that do not map to a property, and there is no "any setter" or
handler that can handle it) should result in a failure (by throwing a
JsonMappingException) or not.
Example of Usage:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
Documentation:
https://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/DeserializationFeature.html
You can also use the annotation #JsonIgnoreProperties on top of the POJO itself: However I am not quite sure if this will throw an Exception on its own.
#Target(value={ANNOTATION_TYPE,TYPE,METHOD,CONSTRUCTOR,FIELD})
#Retention(value=RUNTIME)
public #interface JsonIgnoreProperties
Annotation that can be used to either suppress serialization of properties (during serialization), or ignore processing of JSON properties read (during deserialization).
Example:
// To throw exception on any unknown properties in JSON input:
#JsonIgnoreProperties
public class YourPoJo{
}
public abstract boolean ignoreUnknown
Property that defines whether it is ok to just ignore any unrecognized
properties during deserialization.
If true, all properties that are unrecognized -- that is, there are no
setters or creators that accept them -- are ignored without warnings
(although handlers for unknown properties, if any, will still be
called) without exception.
Does not have any effect on serialization.
Default: false
Documentation: http://fasterxml.github.io/jackson-annotations/javadoc/2.7/com/fasterxml/jackson/annotation/JsonIgnoreProperties.html

Jackson serialising Optional with null value for YAML

Currently I am using the YAMLFactory to configure the ObjectMapper to serialise and deserialise Pojos <=> YAML, however it writes null values in serialisation despite attempting the usual tricks in Jackson.
Annotating with #JsonInclude(JsonInclude.Include.NON_NULL) on the class level or the field level has no affect. I have also tried annotating classes with #JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) with no affect either. How do you achieve this when using the YAMLFactory?
I have found a similar question but the use case does not appear to be the same. To be clear I am trying to omit the field altogether.
Edit: Here is an example (I am also using Lombok)
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
public class QueueProperties extends Properties {
#JsonProperty("QueueName")
private String queueName;
#JsonProperty("RedrivePolicy")
private RedrivePolicy redrivePolicy;
public Optional<RedrivePolicy> getRedrivePolicy() {
return Optional.ofNullable(redrivePolicy);
}
}
when serialized:
Properties:
QueueName: 471416d1-3643-4d5a-a033-65f22757dcaf-chargeback-paypal-ingestion-ingest_dispute
RedrivePolicy: null
ObjectMapper configuration:
ObjectMapper mapper = new ObjectMapper(new YAMLFactory().enable(YAMLGenerator.Feature.MINIMIZE_QUOTES));
mapper.registerModule(new Jdk8Module());
getRedrivePolicy getter method always returns not-null object, even so, Optional could reference to null. In this case, you should skip empty objects. Optional with null is considered as empty and we can use JsonInclude.Include.NON_EMPTY for it. You can keep #JsonInclude(JsonInclude.Include.NON_NULL) on class level and add NON_EMPTY only for given getter:
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public Optional<Object> getRedrivePolicy() {
return Optional.ofNullable(redrivePolicy);
}

Jackson cannot construct instance with one parameter constructor

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

Unable to serialize Object to Json using Jackson

I'm trying to serialize an object in Java using Jackson, but when I'm trying to serialize it, it gives me this error:
No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer
I tried this post, but it didn't help.
Here is the class I'm trying to serialize:
public class Repository {
public String name;
#JsonIgnore // to avoid recursive calls
public ArrayList<UserRole> contributors = new ArrayList<UserRole>();
public User self;
public ArrayList<FileInfo> files;
public RepositoryType repositoryType;
public String path;
}
I also tried to create getters/setters for each field but still nothing.
Here is my serialization method:
public static String convertObjectToJson(Object object) throws IOException {
ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = objectWriter.writeValueAsString(object); //error on this line
return json;
}
Looks like your one of your classes has java.io.FileDescriptor reference.
By default, Jackson will only work with with fields that are either public, or have a public getter methods – serializing an entity that has all fields private or package private will fail
If you look at the source code of java.io.FileDescriptor you can see
there are private fields without public getters.
You should configure your objectMapper visibility to allow access to private fields also.
// For jackson 2.*
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
// For jackson lower than 2
objectMapper.setVisibility(JsonMethod.FIELD, Visibility.ANY);
I was facing problems to send objects to Thymeleaf template with ResponseEntity it was giving me exception "StackOverFlowError" while serializing and your note " #JsonIgnore // to avoid recursive calls" solved my problem. Thanks

java.beans.ConstructorProperties in Jackson 2

My domain Objects are enhanced using lombok, which generates the java.beans #ConstructorProperties annotation for the constructors of immutable objects.
Now in my frontend artifact, I'd like to serialize these objects to JSON using Jackson 2.
For Jackson 1, this could be done using Jackson Extensions. Is there such a solution for Jackson 2 as well or do I have to write it myself?
My main problem is that I want to keep my domain Objects frontend agnostic, so I wouldn't like to pollute them with Jackson annotations.
And no: Java 8 parameter names is not an option, as I am stuck with Java 7 for the time being.
Sean Patrick Floyd has already written a solution, but I am posting my solution because his is proprietary. This is a Jackson module that uses an AnnotationIntrospector to make a constructor annotated with #ConstructorProperties a jackson #JsonCreator.
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.json.PackageVersion;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
public class ConstructorPropertiesModule extends SimpleModule {
public ConstructorPropertiesModule() {
super(PackageVersion.VERSION);
}
#Override
public void setupModule(Module.SetupContext context) {
super.setupModule(context);
context.insertAnnotationIntrospector(new ConstructorPropertiesAnnotationIntrospector());
}
public static class ConstructorPropertiesAnnotationIntrospector extends NopAnnotationIntrospector {
#Override
public boolean hasCreatorAnnotation(Annotated a) {
if (!(a instanceof AnnotatedConstructor)) {
return false;
}
AnnotatedConstructor ac = (AnnotatedConstructor) a;
Constructor<?> c = ac.getAnnotated();
ConstructorProperties properties = c.getAnnotation(ConstructorProperties.class);
if (properties == null) {
return false;
}
for (int i = 0; i < ac.getParameterCount(); i++) {
final String name = properties.value()[i];
final int index = i;
JsonProperty jsonProperty = new JsonProperty() {
#Override
public String value() {
return name;
}
#Override
public boolean required() {
return false;
}
#Override
public Class<? extends Annotation> annotationType() {
return JsonProperty.class;
}
#Override
public int index() {
return index;
}
};
ac.getParameter(i).addOrOverride(jsonProperty);
}
return true;
}
}
}
The module can then be registered to an object mapper to deserialize JSON using the #ConstructorProperties annotation:
ObjectMapper m = new ObjectMapper();
m.registerModules(new ConstructorPropertiesModule());
As others stated Jackson now supports #ConstructorProperties - unfortunatelly. Because it messed up things.
The logic Jackson applies is quite unfortunate. If multiple #ConstructorProperties annotated constructor are present it will create the object via the one with most parameters. Ops. This is problem especially with Lombok which annotates all constructors with #ConstructorProperties. But anyway, this annotation is not there solely for Jackson. It makes sense to annotate every single constructor for any code inspection tool which may use this information. Lombok is right here.
Imagine following object:
#Data
#Builder
#NoArgsConstructor // for Jackson
#AllArgsConstructor // for builder
public class MyDto {
private Type1 value1 = Type1.NONE;
private Type2 value2;
}
Here Jackson will always use the all-args constructor because it is annotated with #ConstructorProperties and has most parameters.
This also means that if you set only value2 in your JSON object the value1 becomes null. Not what you would expect.
Conclusion: the current behaviour (when used with Lombok or annotate more than one constructor) doesn't allow for the easy class-level default values.
Workaround: #AllArgsConstructor(suppressConstructorProperties=true) - but this is claimed to be deprecated soon as it's present just for java 1.5 compatibility purposes.
This issue has finally been resolved in Jackson 2.7 and #ConstructorProperties are now supported out-of-the-box.
See https://github.com/FasterXML/jackson-databind/issues/905
I'm afraid you will have to write a similar wrapper for Jackson2 yourself.

Categories