Jackson ignoring #JsonIgnore? - java

I have a class with a 'read-only' function that shouldn't get serialized into a JSON object. Because the function's name starts with "get", Jackson grabs it, and does not seem to respect the attached #JsonIgnore tag:
import org.codehaus.jackson.annotate.JsonIgnore;
public class MyClass {
publicMyClass() {}
#JsonIgnore
public String getSomeValue() {return "";}
}
I also tried using the following syntax:
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
#JsonIgnoreProperties({"someValue"})
public class MyClass {
publicMyClass() {}
public String getSomeValue() {return "";}
}
In either case the resultant JSON output is the same:
{someValue:""}
(We're using Jackson 1.9.13 with Spring MVC.)
Other than renaming the function to start with something other than "get", what can I do to prevent Jackson from serializing this function's output?

Turns out we were using Jackson 2 to for JSON serialization, so I needed to change the annotation package location.
Jackson 1:
import com.codehaus.jackson.annotation.JsonIgnore
Jackson 2:
import com.fasterxml.jackson.annotation.JsonIgnore
With the fasterxml import everything works as expected.

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

AWS Lambda json deserialization with jackson annotations

I'm calling an aws lambda with a json body. So the fields of the json are with different name from the ones in the POJO. So what I did is to add #JsonProperty on the fields to tell jackson what are the names in json. But for some reason it seems that it doesn't recognize them and all the fields are null. If I pass a json with the same field names as the POJO it's working. Here's my class:
public class Event implements Identifiable {
#JsonProperty("distinct_id")
private String distinctId;
#JsonProperty("user_id")
private Integer userId;
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime eventDateTime;
//Here are the getters and setters
}
If I pass
{"distinct_id":"123", "user_id":123, "dt":"2017-01-04T08:45:04+00:00"}
all the fields are null and with distinctId, userId, eventDateTime it's serializing ok with the exception that it also doesn't recognize my custom serializers/deserializers but this actually is the same problem.
My conclusion is that for some reason the aws jackson is not working with the annotations but it doesn't make sense.
So I found a way to do this. You need to implement RequestStreamHandler which gives you input and output streams which you can work with:
import com.amazonaws.services.lambda.runtime.RequestStreamHandler
public class ChartHandler implements RequestStreamHandler {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
DeserializationClass deserializedInput = objectMapper.readValue(inputStream, DeserializationClass.class)
objectMapper.writeValue(outputStream, deserializedInput); //write to the outputStream what you want to return
}
}
Having the input and output streams makes you independent of the format and frameworks you use to parse it.
Take a look at this quote from AWS documentation:
You shouldn't rely on any other features of serialization frameworks such as annotations. If you need to customize the serialization behavior, you can use the raw byte stream to use your own serialization.
From: https://docs.aws.amazon.com/lambda/latest/dg/java-programming-model-req-resp.html
It sounds like you have version mismatch between annotation types, and databind (ObjectMapper): both MUST be the same major version. Specifically, Jackson 1.x annotations work with Jackson 1.x databind; and 2.x with 2.x.
Difference is visible via Java package: Jackson 1.x uses org.codehaus.jackson, whereas Jackson 2.x uses com.fasterxml.jackson. Make sure to import right annotations for ObjectMapper you use.
I had this same issue and needed MyCustomClass to be taken in and out of the Lambda Function correctly so that it can be passed through my State Machine in the Step Function without any hiccups.
Building off what Hristo Angelov posted, I was able to get a solution that worked for me and I'm posting it hoping that it will help others that were stuck like I was:
import java.io.InputStream;
import java.io.OutputStream;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
public class StaticSearchPagingLambdaFunctionHandler implements RequestStreamHandler {
LambdaLogger logger = null;
MyCustomClass myCustomClass = null;
// Register the JavaTimeModule for LocalDate conversion
ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
#Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) {
myCustomClass = objectMapper.readValue(inputStream, MyCustomClass .class);
// ...
// Do stuff with myCustomClass
// ...
objectMapper.writeValue(outputStream, myCustomClass);
}
}
Even though the JSON string will print out differently with the ObjectMapper writing to the OutPutStream, when the next lambda function takes it in while going through the Step Function, it will still get converted to LocalDate correctly.
Make sure that in MyCustomClass your toString() method prints correctly. My toString() method looks like this:
import java.time.LocalDate;
import org.json.JSONObject;
public class SimpleSearch {
private LocalDate startDate;
private LocalDate endDate;
// ...
// Getters and Setters for the LocalDate variables
// ...
#Override
public String toString() {
return new JSONObject(this).toString();
}
public SimpleSearch() {}
}
then your JSON printouts will always look like this when it gets sent to the lambda and not that other crazy Jackson format:
{
"startDate": "2018-11-01",
"endDate": "2018-11-16"
}
Some of the Maven dependencies I used:
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180813</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.7</version>
</dependency>
Hopefully AWS fixes the Jackson conversions to be reciprocal, to and from JSON, so that we wouldn't have to resort to these custom conversions anymore.
create getter methods for the properties and put #JsonProperty on the getter methods.

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

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.

Spring REST with both JSON and XML

I want to provide one comprehensive REST API with support for both JSON and XML.
The domain model is of complex type and we note that to produce friendly JSON and XML on the same model using MappingJacksonHttpMessageConverter and JaxbMarshaller respectively tends to give either readable XML or readable JSON 1).
What's the best way to proceed?
1) Due to how objects such as maps, root tags and relations are modelled differently in json than in xml, the objects to serialize needs to be designed differently to get both tidy json and tidy xml. Utilities such as jaxb annotations only goes that far.
I can think of a few candidates
1) Create both a json and xml controller/model
public class Controller {
public Foo foo() {
return new Foo();
}
}
public class XmlController extends Controller {
#Override
public XmlFoo foo() {
return new new XmlFoo(super.foo());
}
}
public class JsonController extends Controller {
#Override
public JsonFoo foo() {
return new JsonFoo(super.foo());
}
}
Given a model object Foo create a JsonFoo and XmlFoo
2) Write a custom message converter
I tried this and it turned out to be a bit complicated since the view must know how to resolve e.g., a Foo to a JsonFoo to be able to serialize it into a readable format.
3) Let each model object serialize itself, e.g.,
public class Foo {
public String serialize(Serializer s) {
return s.serialize(this);
}
}
Based on some arbitration parameter let the controller inject the correct serializer
new Foo(new FooJsonSerializer());
new Foo(new FooXmlSerializer());
I'm doing this in a current project without using a ContentNegotiatingViewResolver. For one method in my controller:
#RequestMapping(value = "/test", method = RequestMethod.GET)
#ResponseBody
public HttpEntity<BasicResponse> getBasicResponse() {
return new HttpEntity<BasicResponse>(new BasicResponse());
}
I can receive the following output based on the Accept request header.
Accept: application/xml (requires JAXB2 on the classpath)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<basicResponse>
<errors>
<message>test1</message>
<message>test2</message>
</errors>
</basicResponse>
Accept: application/json (requires Jackson on the classpath)
{
"errors" : ["test1", "test2"]
}
My response object is simple and uses normal annotations:
package org.mypackage.response;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class BasicResponse {
#XmlElementWrapper(name = "errors")
#XmlElement(name = "message")
private List<String> errors = new ArrayList<String>();
public BasicResponse() {
this.errors.add("test1");
this.errors.add("test2");
}
public List<String> getErrors() {
return errors;
}
}
The SpringSource spring-mvc-showcase project is also a helpful resource. I think they separate the conversions for different methods, but I am definitely doing this for one method.
I can't quite tell by your question...but if you're looking to serialize the output more than that, #chrylis is correct in that a custom serializer would be your next move. But everything I've ran into (which can get pretty complex, with nested objects in my response) converts perfectly to valid XML or JSON.
You should use the ContentNegotiatingViewResolver.
There is an issue in that a collection of POJOs are not mapped correctly with some XML marshallers. XStream has solutions for this (Moxy too?).
Here's a place to start:
http://blog.springsource.org/2013/06/03/content-negotiation-using-views/
Basically, you use a MappingJacksonView and a similar one for XML, which is a "fake" view that uses Jackson (or an XML marshaller) to marshall your POJO(s) to the correct format.
The server will send back the correct type based on one of:
the HTTP Accept header
a "filetype extension", such as ".json"
a querystring parameter, such as "format=json"
As far as omitting fields, you cans use annotations #JsonIgnore(for Jackson) and/or #XStreamOmitField(for XStream).
Did you try this:
#RequestMapping(value = "/{id}",
method = RequestMethod.GET,
headers ={"Accept=application/json,application/xml"},
produces={"application/json", "application/xml"})

Categories