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());
Related
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
I've been tasked with converting all of our existing repositories from Joda Time to Java 8 time and I've hit quite a few snags on the way. The first involved DynamoDB not inherently supporting Java 8 so I made a custom converter for ZonedDateTime with some help from the web. Results here:
static public class ZonedDateTimeConverter implements DynamoDBTypeConverter<String, ZonedDateTime> {
#Override
public String convert(final ZonedDateTime time) {
return time.toString();
}
#Override
public ZonedDateTime unconvert(final String stringValue) {
return ZonedDateTime.parse(stringValue);
}
}
So that takes care of marshaling from ZonedDateTime to String and back for DynamoDB. But my last problem now is with marshaling from ZonedDateTime to String for Spring / Jackson dependency injection (I believe. Still pretty new to all this stuff).
Now according to Stackoverflow in order to do that I need jackson-datatype-jsr310 which is here. But at that page it says all I need to be able to marshal ZonedDateTime is Jackson 2.8.5
Now in libs.gradle I can see that we're using Jackson 2.5.0 jackson: 'com.fasterxml.jackson.core:jackson-databind:2.5.0', so that makes sense just need to update it right?
So I update the libs.gradle to now say jackson: 'com.fasterxml.jackson.core:jackson-databind:2.8.5', and I've added libs.jackson to the build.gradle file's compile section: compile(libs.jackson)
But I still receive com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.ZonedDateTime: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?) when building.
I've also tried adding jackson core and jackson databind to the ext.libs definition:
jacksonCore: 'com.fasterxml.jackson.core:jackson-core:2.8.5',
jacksonBind: 'com.fasterxml.jackson.core:jackson-databind:2.8.5',
and the build.gradle:
libs.jacksonCore,
libs.jacksonBind
Still no dice. Any ideas what's going on?
I figured it out! Here's my
build.gradle:
compile(
libs.jacksonCore,
libs.jacksonBind,
libs.jacksonData
)
And libs.gradle:
jacksonCore: 'com.fasterxml.jackson.core:jackson-core:2.8.8',
jacksonBind: 'com.fasterxml.jackson.core:jackson-databind:2.8.8',
jacksonData: 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.8',
I thought jackson-datetype-jsr310 was pre-built into jackson-core or jackson-databind but apparently it isn't.
I've got a very simple problem here, but after using Google for over an hour now, I still cannot find a good solution and it starts to cost too much money..
In my app I use REST as an API, basically only with JSON as payload type, and Enunciate for documentation of the API. As you might know, enunciate will generate an xsd schema from the classes. I am therefore in the situation, that I need to configure all the DTO's for Jackson/JSON handling with the suitable annotations, and also with JAXB annotation to ensure the generated schema is correct and can be parsed with XJC into the correct classes!
Although that is not very difficult to achieve and works flawless in most of the cases, I have a simple but somehow special case, in which it completely fails.
Assuming the following classes:
#JsonRootName(value = "location")
public class Location {
private String label;
#JsonUnwrapped
private Address address;
// constructors, getters, setters ommited..
}
//// new file
public class Address{
private String city;
private String street;
private String postCode;
}
This works 100% with Jackson/JSON. The embedded Address object will be unwrapped so that the JSON looks like this:
{
"label":"blah",
"street":"Exchange Road",
"city":"Stacktown"
"postCode":"1337"
}
This works forth and back with Jackson.
JAXB on the other hand is able to parse (most of) the Jackson annotations, so that generally you wont have problems with using simple objects in both worlds. #JsonUnwrapped though sadly is NOT supported by JAXB, and strangely that (from my POV) quite simple usecase seems to be not reflectable with any JAXB annotation at all.
What happens is, that the generated schema contains the embedded Address object, without any attributes/elements. Therefore the class generated by XJC will contain that Address object reference. And this ultimately leads to erroneous JSON from a app that will use the schema to generate objects...
Any ideas?
The JAXB (JSR-222) specification does not define an equivalent to Jackson's #JsonUnwrapped. For a long time we have offered that functionality in the EclipseLink MOXy implementation of JAXB via our #XmlPath extension.
#XmlPath(".")
private Address address;
For More Information
I have written more about MOXy's #XmlPath extension on my blog:
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
I am actually trying to build a process for refactoring a large number of existing webservices.
The idea is to use JAXB/JAXWS tools to automate this as much as possible.
Most of our issues are resolved except for one blocking problem :
JAXB by default serializes boolean types as "true"/"false". I need those values to be "0"/"1".
On the existing classes of our codebase, I'm trying to use as much annotations as possible, and of course not modifying at all what is auto generated by JAX tools.
After adding correct annotations, I run wsgen so it generates JAX-WS necessary classes for deployment.
Here is a concrete example of an annotated webservice method:
public #WebResult(name = "success")
#XmlJavaTypeAdapter(value = lu.ept.common.xmlSerializers.BooleanAdapter.class, type = boolean.class)
boolean modifyStatus(#WebParam(name = "processActionId") String processActionId, #WebParam(name = "newStatus") int newStatus)
throws BusinessMessage, SystemMessage {
When running wsgen, it picks up correctly the WebResult and WebParam attributes, but it refuses to use my XmlJavaTypeAdapter.
My question is very simple : has someone managed to use XmlJAvaTypAdapter annotation on a webservice method return value? Is it possible?
(on the documentation I read, I haven't seen anything concerning the use of that annotation on return values)
Actually the only solution I have found is to manually add the XmlJavaTypeADapter annotation to the classes generated by wsgen. This is not a viable solution, because those classes will be generated after each build...
Consider the following classes (please assume public getter and setter methods for the private fields).
// contains a bunch of properties
public abstract class Person { private String name; }
// adds some properties specific to teachers
public class Teacher extends Person { private int salary; }
// adds some properties specific to students
public class Student extends Person { private String course; }
// adds some properties that apply to an entire group of people
public class Result<T extends Person> {
private List<T> group;
private String city;
// ...
}
We might have the following web service implementation annotated as follows:
#WebService
public class PersonService {
#WebMethod
public Result<Teacher> getTeachers() { ... }
#WebMethod
public Result<Student> getStudents() { ... }
}
The problem is that JAXB appears to marshall the Result object as a Result<Person> instead of the concrete type. So the Result returned by getTeachers() is serialized as containing a List<Person> instead of List<Teacher>, and the same for getStudents(), mutatis mutandis.
Is this the expected behavior? Do I need to use #XmlSeeAlso on Person?
Thanks!
LES
The answer to this one was rather tricky. It turns out that the version of jettison used by the jax-ws json plugin is a bit old (1.0-beta-1 IIRC). That particular version doesn't handle this case well (it crashes). If you do add the #XmlSeeAlso then the JSON marshalling will crash! Of course, this sucks!
I read on some forum (don't have the link - this is all from memory) that the jax-ws json plugin isn't actively maintained. If you try to 1) exclude the default jettison dependency from the jax-ws json depedency (assuming maven here) and add a newer version you get an error about JSONException not existing. Jax-ws json will NOT work with a newer version of jettison (I tried).
This is documented on another website (someone stated that they would like for someone to port jax-ws json to the latest jettison).
In the end, I switched to DWR for remoting. JAX-WS is best left for system-to-system (backend) integration. It's too heavy-weight for front-end stuff. DWR works AWESOMELY in this case.
Found link to forum I read: http://forums.java.net/jive/thread.jspa?messageID=384385 . Note that Collab.net is currently down for maintenance but they'll be back up soon.
As far as answering the general question (without the JAX-WS JSON plugin part), the answer is yes - you must use the #XmlSeeAlso annotation on the Person class. Otherwise the schema will only contain Person and not Teacher or Student elements. Only the elements defined in Person are marshalled without the #XmlSeeAlso annotation.