Jackson serializes using #JsonProperty name in constructor - java

I have been working on upgrading the Spring Boot version of one of my microservices, and I stumbled upon a strange behaviour. I have class like this:
public class FilteredData {
private final List<ShipmentData> shipments;
public FilteredData(#JsonProperty("listShipments") List<ShipmentData> shipments) {
this.shipments = shipments;
}
public List<ShipmentData> getShipments() {
return shipments;
}
}
The behaviour that I had before doing the upgrade was that, when deserialising, the name listShipments was used in the JSON object to map to the shipments property of the Java class. However, when serialising, it would write the shipments property with the name shipments, not listShipments.
The problem is that now it is using the name listShipments when both deserialising and serialising. I am not sure at what point this issue started happening, as my initial Spring Boot version was 1.5.7 and I am slowly upgrading all the way to 2.3.4. But I believe it started happening after version 2.0.0.
I don't know if this is being caused by some internal change in Spring Boot's Jackson autoconfiguration, or a change in the actual Jackson library, but I am having a hard time tracking what caused this and how to fix it.
EDIT: I noticed from the latest Spring Boot 1 version (1.5.22) to Spring Boot 2.0.0, the Jackson minor version was bumped (from 2.8 to 2.9). Could this have caused the issue?

I was able to reproduce this exact behaviour while switching from Spring Boot 1.5.7.RELEASE to 2.0.0.RELEASE. First, I added the parent and the spring-boot-starter-web dependency. Then, I created a simple #RestController, used the POJO you provided and replaced ShipmentData with String. When I manually create a Jackson ObjectMapper, I could not reproduce the issue across the two releases.
The most interesting part: When you rename the parameter name of the constructor to something other than shipments, it seems to work fine. This could possibly be a bug in Jackson or somewhere in the Spring Framework which manifests only when the getter matches the parameter name in the constructor.
I suggest to use #JsonAlias to define the alternative name that should be accepted during deserialization:
#JsonCreator
public FilteredData(
#JsonProperty("shipments")
#JsonAlias("listShipments")
List<ShipmentData> shipments) {
this.shipments = shipments;
}
With this, you can deserialize with both listShipments and shipments while for serialization only shipments is used (defined by getter).
For reference, a working implementation with 2.3.3.RELEASE:
#RestController
public class FilteredDataController {
#PostMapping("/data")
public FilteredData postFilteredData(#RequestBody FilteredData data) {
return data;
}
}
public class FilteredData {
private final List<String> shipments;
#JsonCreator
public FilteredData(
#JsonProperty("shipments") #JsonAlias("listShipments")
List<String> shipments) {
this.shipments = shipments;
}
public List<String> getShipments() {
return shipments;
}
}
Request:
curl -d '{"listShipments":["a","b","c"]}' -H 'Content-Type: application/json' http://localhost:8080/data
Result:
{"shipments":["a","b","c"]}

I managed to find the issue! It was caused by a dependency being used by spring-boot-starter-web called jackson-module-parameter-names. This library provides a Jackson module named ParameterNamesModule, which, as per Spring Boot's Jackson Autoconfiguration, any Jackson module found in the classpath is automatically imported.
The description for this library in https://github.com/FasterXML/jackson-modules-java8 says:
Parameter names: support for detecting constructor and factory method ("creator") parameters without having to use #JsonProperty annotation. Provides com.fasterxml.jackson.module.paramnames.ParameterNamesModule
I am not 100% sure still why this created the problem that it did, but removing it through Maven solved the issue:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</exclusion>
</exclusions>
</dependency>

There were breaking changes between Spring Boot 1 and Spring Boot 2. Have you tried anything to fix it already? E.g. attempted to move the JsonProperty annotation to field rather than constructor? or using #JsonCreator
online resource: https://www.baeldung.com/jackson-deserialize-immutable-objects

Related

Java 14 records json serialization

Currently experimenting with the Records implements from java 14, everything looks nice but since the accessors are slightly different and jackson is not being able to deserialize and giving the following error:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.x.x.x.xTracking and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
I checked all around the internet, including jackson and gson github to check the jep 359 support but havent found a single comment. Am i missing something really straight forward?
Yes i am aware that the java 14 is still not released and that records is only in preview in this version but would expect some comments at least.
Records support was added to Jackson 2.12.0 (https://github.com/FasterXML/jackson-future-ideas/issues/46). It will be released in the next days.
For someone else experimenting, i went around, not proudly, with the following:
#Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer(){
return builder ->
builder.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
With "pure" Json-B you can do it this way:
public class RecordPropertyVisibilityStrategy implements PropertyVisibilityStrategy {
#Override
public boolean isVisible(Field field) {return true;}
#Override
public boolean isVisible(Method method) {return false;}
}
Then
#JsonbVisibility(RecordPropertyVisibilityStrategy.class)
public record MyRecord(Long id, String attr) {}
I did Jackson dealing with this problem in Spring Boot 2.4.1 application annotating the record with
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
Alternatively, for Spring Boot 2.4.5 and Java 16, it may be enabled via application.properties, like so:
spring.jackson.visibility.field=any
See more on the Spring Boot documentation.

#JsonSerializer not working after migrating from v1 Jackson to 2.4.4, like it is being ignored

I am running on JBoss 6.1.4 and was using Jackson V1. After changing my dependencies to reference version 2.4.4 (and changing all includes to fasterxml versions), it is like the #JsonSerialize is being ignored. Instead of a String (from the serializer), it is returning the class as JSON that should have been serialized. Logging and System.out inside the serializer is not showing up.
I made no changes to code other than changing includes to use fasterxml.
public class HiDateSerializer extends JsonSerializer<HiDate> {
#Override
public void serialize(final HiDate value, final JsonGenerator gen, final SerializerProvider sp) throws IOException, JsonProcessingException {
if (value == null || value.isNull()) {
gen.writeString("");
} else {
gen.writeString(value.fmt());
}
}
}
Then in my model class, I use annotation for on all HiDate attributes:
#JsonSerialize(using = HiDateSerializer.class)
private HiDate dob;
Here is my dependency:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
What I was getting previously was either an empty string or a string with just the date. However, now I am getting a JSON representation of the entire HiDate class. It is as if the #JsonSerialize is not being honored anymore.
I guess, you use default ResteasyJacksonProvider which loads classes from org.codehaus.*. You need to register ResteasyJackson2Provider which uses classes from com.fasterxml.jackson.* packages.
See also:
jackson jboss eap6.1.1 annotations not working
Chapter 21. JSON Support via Jackson
JBoss resteasy - Custom Jackson provider
Using resteasy-jackson2-provider instead of resteasy-jackson-provider
jackson-jaxrs-providers

#jsonRootName not working with spring boot starter hateoas

I am developing an rest application using spring-boot and using spring-Hateoas . And the DTO that i have written is:
Bill.java
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName("bills")
public class Bill{
Depedencies:
dependencies {
compile "org.springframework.boot:spring-boot-starter-hateoas"
compile "org.springframework.boot:spring-boot-starter-ws"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "org.springframework.cloud:spring-cloud-starter-eureka:${springCloudVersion}"
testCompile("org.springframework.boot:spring-boot-starter-test")
}
Application.java:
#Configuration
#Import(BillServiceConfig.class)
#EnableAutoConfiguration
#EnableEurekaClient
#ComponentScan({"com.billing"})
#EnableWebMvc
#EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
public class Application {
BillController.java:
#RequestMapping(method = RequestMethod.GET, value = "")
public ResponseEntity<Resources<Resource<Bill>>> getBills(#PathVariable String billUid)
And the spring-boot version I am using is 1.2.2. The output that I am getting is
`_embedded: {
BillList:
{`
The json Root name here is BillList. But I need it as "bills" instead of "BillList". Can anybody help out in this issue. Thanks in advance.
The keys within an _embedded clause are actually relation names. Hence, they're obtained though a RelProvider abstraction in Spring HATEOAS. The easiest way to customize them is by annotating the domain type with #Relation and define the relation names that you expect for item and collection relations.
An easy way to get correct plurals used in the _embedded clause is by adding the Evo Inflector JAR to your classpath as documented here.

Jackson empty xml array deserialization

I have an incoming xml from Recurly service with list of transactions. Sometimes it's empty and looks like this:
<transactions type="array">
</transactions>
I need to deserialize this using Jackson. I've tried next mapping
#XmlRootElement(name = "transactions")
public class TransactionObjectListResponse extends ArrayList<TransactionObjectResponse> {
}
where TransactionObjectResponse class for each transaction. It works fine for non-empty collections, but fails when no transactions came. Next message appears:
java.lang.IllegalStateException: Missing name, in state: END_ARRAY
at com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser.getCurrentName(FromXmlParser.java:310)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:289)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:157)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:123)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:230)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:207)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:23)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2888)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2034)
I used XmlMapper directly,
xmlMapper.readValue(responseXml, TransactionObjectListResponse.class);
Response entity structure isn't strict, any help would be appricated. Thanks.
I had a similar issue running the 2.2 version of "jackson-dataformat-xml". I took this one library down a version and it worked.
ie. (if you are using Maven)
<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-xml</artifactId>
  <version>2.2.3</version>
</dependency>
This is the same version as the 3rd party java recurly library uses - http://search.maven.org/#artifactdetails%7Ccom.ning.billing%7Crecurly-java-library%7C0.1.6%7Cjar

jackson annotations being ignored

I'm trying to use Jackson annotations to re-name some of the json labels produced during serialization. The annotations all compile fine, and when I run, the Jackson serialization works except all Jackson annotations are completely ignored. Even the basic ones like #JsonIgnore or #JsonProperty have no effect on the json response. The libraries I have in my build path are:
jsr311-qpi-1.1.1.jar
jackson-[core|databind|annotations]-2.2.0.jar
I'm running within Eclipse running the jetty external program with the External program setup as:
Location: .../apache-maven-2.2.1/bin/mvnDebug.bat
working Directory: ${workspace_loc:/ohma-rest-svr}
Arguments: jetty:run
with the Remote Java Application configuration set as:
Host: localhost
Port: 8000
With no error messages to work from, I'm at a bit of a loss of things to try. Any ideas would be appreciated.
Here's a bit of code sample from a class I need to serialize:
#XmlRootElement(name="ads-parameter")
public class DefineParameterResponse {
private Date _createdAt = new Date();
#JsonProperty("created-at")
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd,HH:00", timezone="CET")
#XmlElement
public String getCreatedAt() {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(_createdAt);
}
#JsonProperty("created-at")
public void setCreatedAt(Date createdAt) {
this._createdAt = createdAt;
}
private String _dataTitle1 = "Default Title1";
#XmlElement
#JsonProperty("data-title-1")
public String getDataTitle1() {
return _dataTitle1;
}
#JsonProperty("data-title-1")
public void setDataTitle1(String dataTitle1) {
this._dataTitle1 = dataTitle1;
}
#XmlElement
#JsonProperty("data-title-2")
public String getDataTitle2() {
return _dataTitle2;
}
#JsonProperty("data-title-2")
public void setDataTitle2(String dataTitle2) {
this._dataTitle2 = dataTitle2;
}
One relatively common reason is trying to use "wrong" set of annotations: Jackson 1.x and Jackson 2.x annotations live in different Java packages, and databind has to match major version. This design has the benefit of allowing 1.x and 2.x versions to be used side by side, without class loading conflicts; but downside that you have to make sure that you have matching versions.
Biggest problem is the use by frameworks: many JAX-RS implementations (like Jersey) still use Jackson 1.x by default. So I am guessing you might be using Jackson 1.x indirectly, but adding Jackson 2.x annotations. If so, you need to use 1.x annotations (ones under org.codehaus.jackson) instead.
Just in case that somebody hits a similiar problem where only #JsonFormat gets ignored:
Consider that in Spring Boot + Java 8 context the processing of LocalDateTime may experience troubles. Instead of following dependency:
compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jdk8'
You should use:
compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310'
Furthermore, when you create your Jackson ObjectMapper you need to register the implementation which treats LocalDateTime correctly:
json = new ObjectMapper().findAndRegisterModules().writeValueAsString(entity);
new ObjectMapper().findAndRegisterModules().readValue(json, entity.getClass());

Categories