We've defined a model in our service code as -
#JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class SomeData {
public boolean tnAvailable;
#NonNull
public String sTempChange;
public boolean isTnAvailable() {
return faAvailable;
}
public void setTnAvailable(boolean faAvailable) {
this.faAvailable = faAvailable;
}
#Nonnull
public String getSTempChange() {
return sTempChange;
}
public void setSTempChange(#Nonnull String sTempChange) {
this.sTempChange = sTempChange;
}
}
When the api including the above model in response is queried , we get the response as -
"someData": {
"tnAvailable": true,
"stempChange": "trial_001"
}
What surprised us was the stempChange(notice lowercase t) instead of sTempChange in the attributes of the response.
Suspecting the cause to be Jackson com.fasterxml.jackson.core:jackson-core:2.5.2 while serializing and deserializing of the objects during API calls since we do not alter the attribute using any other getter-setter ot wrapper.
Why would this so happen and is serialization/deserialization the correct direction to look for this?
Edit - From the comment by #Windle, trying to explain what's different here. I re-iterate "The question though there relates pretty much to the same situation. Yet I 'm looking forward to the reason's for such implementation and documentation in fasterxml as well."
Handling of multiple leading capital letters in getters/setters (like "getURL()", or "getFName()").
By default, Jackson will simply lower-case ALL leading upper-case letters, giving "url" and "fname".
But if you enable MapperFeature.USE_STD_BEAN_NAMING (added in Jackson 2.5), it will follow what Java Bean naming convention would do, which is only lower-case a single upper-case leading letter; if multiple found, do nothing.
That would result in properties "URL" and "FName".
Yeah it looks like it's getting confused on the method name. You can force the serialized name with the #JsonGetter annotation
#JsonGetter("sTempChange")
public String getSTempChange() {
return sTempChange;
}
When I first tried out your SomeData class and serialized it I got the following results:
{"tnAvailable":true,"sTempChange":"trial_000","stempChange":"trial_000"}
This means that jackson doesn't match your getters/setters with the sTempChange property and they are treated as different properties. After adding the following configuration for my mapper I was able to reproduce your case:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.ANY);
objectMapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.ANY);
objectMapper.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.ANY);
Now the reason for your error is because Jackson uses its own implementation of bean utilities (com.fasterxml.jackson.databind.util.BeanUtil) which is used when a class is processed for fields, getters and setters (done by com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector) when an instance is serialized/deserialized. Methods of interests are okNameForGetter and okNameForSetter. In those methods there are 2 other methods used depending on the MapperFeature.USE_STD_BEAN_NAMING (it is passed in the stdNaming argument in all methods). The two methods are used in the following manner:
return stdNaming
? stdManglePropertyName(name, prefix.length())
: legacyManglePropertyName(name, prefix.length());
The stdManglePropertyName follows the Java Beans specification in section 8.8 and the legacyManglePropertyName does not and is used in versions prior to 2.5 of Jackson.
Now after running your getter and setter method names through this methods, however you set MapperFeature.USE_STD_BEAN_NAMING, your getter/setter for sTempChange property is wrongly named. It should be getsTempChange (lowercase 's') and getsTempChange (again lowercase 's') to correctly serialize and deserialize the instances of SomeData class.
Finally here is some code for testing:
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test {
static class SomeData {
public boolean tnAvailable;
public String sTempChange;
public String getsTempChange() {
return sTempChange;
}
public void setsTempChange(String sTempChange) {
this.sTempChange = sTempChange;
}
public boolean isTnAvailable() {
return tnAvailable;
}
public void setTnAvailable(boolean tnAvailable) {
this.tnAvailable = tnAvailable;
}
}
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
// objectMapper.configure(MapperFeature.USE_STD_BEAN_NAMING, true);
SomeData someData = new SomeData();
someData.setsTempChange("trial_000");
someData.setTnAvailable(true);
// objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
// objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.ANY);
// objectMapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.ANY);
// objectMapper.setVisibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.ANY);
try {
System.out.println("Serialize: " + objectMapper.writeValueAsString(someData));
String json = "{ \"tnAvailable\": false, \"sTempChange\": \"trial_001\" }";
SomeData anotherData = objectMapper.readValue(json, SomeData.class);
System.out.println("Deserialize: " + anotherData.isTnAvailable() + ", " + anotherData.getsTempChange());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Related
I am using the Jackson library for conversion of a JSON string to Java objects
My json is:
{
"human":{
"fname":"anjali",
"lname":"malhotra"
}
}
I want this to be converted into a Java class with following structure:
public class Human
{
String fname;
String lname;
}
I can successfully convert it into
public class HumanWrapper
{
Human human;
}
But, I wanted to know if there is a way I can directly convert it into the Human format. I read about custom deserialization but was reluctant for that approach.
You could achieve this by configuring ObjectMapper to use DeserializationFeature.UNWRAP_ROOT_VALUE :
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
and annotatating your Human class with #JsonRootName annotation :
#JsonRootName("human")
public class Human {
....
}
You need to have a HumanWrapper class as your human object is defined inside you json object
{
"human": {
}
}
If you able to change you API to send just a human object like this
{
"fname":"anjali",
"lname":"malhotra"
}
Then you woudn't be bothering to have a HumanWrapper
My Spring MVC Web Service code is as follows.
Model Class
#XmlRootElement(name="wrappedSecretData")
public class VendorData {
private long lKeyId;
#XmlElement(name="keyId")
public long getlKeyId() {
return lKeyId;
}
public void setlKeyId(long lKeyId) {
this.lKeyId = lKeyId;
}
}
Controller Method
#RequestMapping(value = "/vendor", method = RequestMethod.POST)
public String addVendor(#RequestBody VendorData vendorData) {
/*Checking recieved value*/
System.out.println(vendorData.getlKeyId());//**Returning 0 value **
return "Success";
}
Xml request body for web service
<wrappedVendorSecretsMetadata>
<keyId>1</keyId>
</wrappedVendorSecretsMetadata>
I am getting "0" value in lKeyId(Bold comment).
Where am I doing wrong.
Please provide the correct way to bind the xml element to object member using #XmlElement(name="keyId") annotation.
I think you need the #XmlElement only over the variable declaration.
try this:
#XmlRootElement(name="wrappedVendorSecretsMetadata")
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
public class VendorData {
private long lKeyId;
public VendorData(){
}
#XmlElement(name="keyId")
public long getlKeyId() {
return lKeyId;
}
public void setlKeyId(long lKeyId) {
this.lKeyId = lKeyId;
}
}
By default, annotations doesn't work with XmlMapper in jaxb. You have to register the annotation module for this purpose as I have done in the following code block:
String xmlData = getMyXmlData();
ObjectMapper objectMapper = new XmlMapper();
objectMapper.registerModule(new JaxbAnnotationModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MyClass myObj= objectMapper.readValue(xmlData , MyClass.class);
In your case, you have to overwrite the Xml to Object binding process. To do that, you can receive the the HttpRequest in your controller and then convert the xml data to VendorData using your own java code.
I have ServerDetails pojo class.
package org.vo;
import org.codehaus.jackson.annotate.JsonProperty;
public class ServerDetails {
private Integer serverId;
private String server_url;
public ServerDetails() {
}
public ServerDetails(Integer serverId, String server_url) {
this.serverId = serverId;
this.server_url = server_url;
}
#JsonProperty("server-id")
public Integer getServerId() {
return serverId;
}
public void setServerId(Integer serverId) {
this.serverId = serverId;
}
#JsonProperty("server-url")
public String getServer_url() {
return server_url;
}
public void setServer_url(String server_url) {
this.server_url = server_url;
}
}
I converted the ServerDetails object to JSON using Jackson API and to customize the JSON field name I used #JsonProperty annotation. So my generated JSON is as expected like
{
"server-id":1,
"server-url":"http://stackoverflow.com"
}
Although in pojo class the properties are serverId and server_url but in generated JSON the fields are server-id and server-url as I have applied the #JsonProperty annotation on both propertie's getter method.
All are ok still now.
But when I am trying to covert the same JSON to Java, I am getting below error
org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "server-id" (Class org.vo.ServerDetails), not marked as ignorable
at [Source: D:\tmp\ServerDetails.json; line: 1, column: 15] (through reference chain: org.vo.ServerDetails["server-id"])
at org.codehaus.jackson.map.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:53)
at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:244)
at org.codehaus.jackson.map.deser.StdDeserializer.reportUnknownProperty(StdDeserializer.java:605)
at org.codehaus.jackson.map.deser.StdDeserializer.handleUnknownProperty(StdDeserializer.java:591)
at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:684)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:515)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:351)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2130)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1348)
at org.converter.JSONToJavaExample.main(JSONToJavaExample.java:16)
Somehow I am unable to figure it out what I need to do to fix this issue.I just want to get back the ServerDetails object from the generated JSON.
Below is my code for JSON to java object conversion.
import java.io.File;
import java.io.IOException;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.vo.ServerDetails;
public class JSONToJavaExample {
public static void main(String[] args) {
ServerDetails serverDetails = null;
ObjectMapper mapper = new ObjectMapper();
try {
serverDetails = mapper.readValue(new File(
"D:/tmp/ServerDetails.json"), ServerDetails.class);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
I am using jackson-all-1.7.6.jar and jdk1.8.0_31.
The issue has been resolved and full credit goes to both sotirios-delimanolis and staxman.Generally people like to find direct answer and not really like to going through the comments although the actual answer can be present among those comments. So this answer is dedicated for those kinds of users who like to find the direct answers.
If you are using Jackson to convert JSON to/from Java object and the Jackson API version is lower than 1.8 then you need to annotate both the getter and setter method with #JsonProperty if JSON field names and Java property names are different.
So, I have annotated both the setter methods with #JsonProperty annotation. Below is the modified code (ServerDetails.java)
#JsonProperty("server-id")
public Integer getServerId() {
return serverId;
}
// added
#JsonProperty("server-id")
public void setServerId(Integer serverId) {
this.serverId = serverId;
}
#JsonProperty("server-url")
public String getServer_url() {
return server_url;
}
// added
#JsonProperty("server-url")
public void setServer_url(String server_url) {
this.server_url = server_url;
}
If you want to avoid this extra effort, then you need to upgrade your jackson-all jar version and the version should be higher than 1.7.
Why? Why higher than 1.7?
In staxman's words
key difference is 1.7 vs 1.8: latter added code to "unify" annotations, so you do not need to add renaming for both getter and setter. With 1.7 you would need to add annotation for both, and your class only had them for one. So, not technically a bug (it was defined behavior), but missing functionality.
Hope this answer will help someone who is facing the same problem.
I am trying to be able to define the following code:
public class MyObject {
private String name;
... // Other attributes
}
#Path(...)
#Stateless
public class MyRestResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response create(List<MyObject> myObjects) {
// Do some stuff there
}
}
I know that I need to use:
DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true
to setup correctly my object mapper to be able to accept single value as array on my rest resources. I succeed to setup that part.
My problem with this approach is that the following content is not differentiable:
{
"name": "a name",
... // other attributes
}
and
[{
"name": "a name",
... // other attributes
}]
will result into a list (List) of size one. Then, in the method create(List myObjects), I will not be able to do the difference between the List and the Single Object sent to the Rest Resource.
Then, my question is how to do something like that. The idea is to have only one #POST that accepts both Arrays and Single values?
Ideally, I will get rid of the configuration of the ObjectMapper to avoid letting the possibility to set Single Object into the other level of the JSON document. For example, I do not want to allow that:
{
...
"attributes": {
...
}
}
where normally this format should be mandatory:
{
...
"attributes": [{
...
}]
}
Based on that, I tried to put in place an object wrapper of my List to set if I am able to the difference between the list and the object. With something like that:
public class ObjectWrapper<T> {
private List<T> list;
private T object;
public boolean isObject() {
return list == null;
}
}
with the resource that becomes:
#Path(...)
#Stateless
public class MyRestResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response create(ObjectWrapper myObjects) {
// Do some stuff there
}
}
and trying to put in place the deserialization of my content through the JAX-RS/Jersey/Jackson mechanisms. If I let the solution as it is now, the deserialization fails due to the fact that the JSON format expected is the following:
{
"list": [{
"name": "a name",
... // other attributes
}]
}
Then I tried to write a custom deserializer but I am a bit lost in this task. I have something like that:
public class ObjectWrapperDeserializer<T> extends JsonDeserializer<T> {
#Override
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
... // What to put there to deserialize Array or Object
}
}
I just want to deserialize the root level to set the content deserialized into the object wrapper. I also want to keep the feature configured in a class annotated with #ApplicationPath when the configuraiton of the different #Provider are done.
I hope that all the info will give a sufficient picture of what I want to do and what I already tested.
Waiting for suggestion on how to do a resource that accept Arrays or Objects on the same path.
Thanks a lot in advance.
Ok, finally I succeed to put in place a mechanism that do exactly what I am looking for. But, I am not sure if there are negative consequences such the performance or such things.
First, I defined a class that can accept both List or Single Object:
public class RootWrapper<T> {
private List<T> list;
private T object;
}
Then, I need a custom deserializer that is able to know which kind of T type to deserialize and to handle the collection or the single object.
public class RootWrapperDeserializer extends JsonDeserializer<CollectionWrapper<?>> {
private Class contentType;
public RootWrapperDeserializer(Class contentType) {
this.contentType = contentType;
}
#Override
public RootWrapper deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// Retrieve the object mapper and read the tree.
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode root = mapper.readTree(jp);
RootWrapper wrapper = new RootWrapper();
// Check if the root received is an array.
if (root.isArray()) {
List list = new LinkedList();
// Deserialize each node of the array using the type expected.
Iterator<JsonNode> rootIterator = root.getElements();
while (rootIterator.hasNext()) {
list.add(mapper.readValue(rootIterator.next(), contentType));
}
wrapper.setList(list);
}
// Deserialize the single object.
else {
wrapper.setObject(mapper.readValue(root, contentType));
}
return wrapper;
}
}
As far as I know, I try to only deserialize the root level manually and then let Jackson take the next operations in charge. I only have to know which real type I expect to be present in the Wrapper.
At this stage, I need a way to tell Jersey/Jackson which deserializer to use. One way I found for that is to create a sort of deserializer registry where are stored the type to deserialize with the right deserializer. I extended the Deserializers.Base class for that.
public class CustomDeserializers extends Deserializers.Base {
// Deserializers caching
private Map<Class, RootWrapperDeserializer> deserializers = new HashMap<>();
#Override
public JsonDeserializer<?> findBeanDeserializer(JavaType type,
DeserializationConfig config, DeserializerProvider provider,
BeanDescription beanDesc, BeanProperty property) throws JsonMappingException {
// Check if we have to provide a deserializer
if (type.getRawClass() == RootWrapper.class) {
// Check the deserializer cache
if (deserializers.containsKey(type.getRawClass())) {
return deserializers.get(type.getRawClass());
}
else {
// Create the new deserializer and cache it.
RootWrapperDeserializer deserializer =
new RootWrapperDeserializer(type.containedType(0).getRawClass());
deserializers.put(type.getRawClass(), deserializer);
return deserializer;
}
}
return null;
}
}
Ok, then I have my deserializers registry that create new deserializer only on demand and keep them once created. What I am not sure about that approach is if there is any concurrency issue. I know that Jackson do a lot of caching and do not call every time the findBeanDeserializer once it was called a first time on a specific deserialization context.
Now I have created my different classes, I need to do some plumbing to combine everything together. In a provider where I create the ObjectMapper, I can setup the deserializers registry to the created object mapper like below:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JsonObjectMapper implements ContextResolver<ObjectMapper> {
private ObjectMapper jacksonObjectMapper;
public JsonObjectMapper() {
jacksonObjectMapper = new ObjectMapper();
// Do some custom configuration...
// Configure a new deserializer registry
jacksonObjectMapper.setDeserializerProvider(
jacksonObjectMapper.getDeserializerProvider().withAdditionalDeserializers(
new RootArrayObjectDeserializers()
)
);
}
#Override
public ObjectMapper getContext(Class<?> arg0) {
return jacksonObjectMapper;
}
}
Then, I can also define my #ApplicationPath that is my REST application like following:
public abstract class AbstractRestApplication extends Application {
private Set<Class<?>> classes = new HashSet<>();
public AbstractRestApplication() {
classes.add(JacksonFeature.class);
classes.add(JsonObjectMapper.class);
addResources(classes);
}
#Override
public Set<Class<?>> getClasses() {
return classes;
}
#Override
public Set<Object> getSingletons() {
final Set<Object> singletons = new HashSet<>(1);
singletons.add(new JacksonJsonProvider());
return singletons;
}
private void addResources(Set<Class<?>> classes) {
classes.add(SomeRestResource.class);
// ...
}
}
Now, everything is in place and I can write a REST resource method like that:
#POST
#Path("somePath")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response create(RootWrapper<SpecificClass> wrapper) {
if (wrapper.isObject()) {
// Do something for one single object
SpecificClass sc = wrapper.getObject();
// ...
return Response.ok(resultSingleObject).build();
}
else {
// Do something for list of objects
for (SpecificClass sc = wrapper.getList()) {
// ...
}
return Response.ok(resultList).build();
}
}
That's all. Do not hesitate to comment the solution. Feedbacks are really welcome especially around the way of deserialization process where I am really not sure that it is safe for performance and concurrency.
i have an class with the following annotations:
class A {
public Map<String,List<String>> references;
#JsonProperty
public Map<String,List<String>> getReferences() {
...
}
#JsonIgnore
public void setReferences(Map<String,List<String>>) {
}
...
}
}
What I try is to ignore the json on deserialization. But it doesn't work. Always when JSON String arrives the Jackson lib fill the references attribute. If I use only the #JsonIgnore annotation the getter doesn't work. Are there any solutions for this problem?
Thanks
I think there are two key pieces that should enable you to have "read-only collections" as desired. First, in addition to ignoring the setter, ensure that your field is also marked with #JsonIgnore:
class A {
#JsonIgnore
public Map<String,List<String>> references;
#JsonProperty
public Map<String,List<String>> getReferences() { ... }
#JsonIgnore
public void setReferences(Map<String,List<String>>) { ... }
}
Second, in order to prevent the getters from being used as setters, disable the USE_GETTERS_AS_SETTERS feature:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.USE_GETTERS_AS_SETTERS);
As of Jackson 2.6, there is a new and improved way to define read-only and write-only properties, using JsonProperty#access() annotation. This is recommended over use of separate JsonIgnore and JsonProperty annotations.
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Map<String,List<String>> references;
You have to make sure there is #JsonIgnore annotation on the field level as well as on the setter, and getter annotated with #JsonProperty.
public class Echo {
#Null
#JsonIgnore
private String doNotDeserialise;
private String echo;
#JsonProperty
public String getDoNotDeserialise() {
return doNotDeserialise;
}
#JsonIgnore
public void setDoNotDeserialise(String doNotDeserialise) {
this.doNotDeserialise = doNotDeserialise;
}
public String getEcho() {
return echo;
}
public void setEcho(String echo) {
this.echo = echo;
}
}
#Controller
public class EchoController {
#ResponseBody
#RequestMapping(value = "/echo", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public Echo echo(#RequestBody #Valid Echo echo) {
if (StringUtils.isEmpty(echo.getDoNotDeserialise())) {
echo.setDoNotDeserialise("Value is set by the server, not by the client!");
}
return echo;
}
}
If you submit a JSON request with a “doNotDeserialise” value set to something, when JSON is deserialised to an object it will be set to null (if not I put a validation constraint on the field so it will error out)
If you set the “doNotDeserialise” value to something on the server then it will be correctly serialised to JSON and pushed to the client
I used #JsonIgnore on my getter and it didn't work and I couldn't configure the mapper (I was using Jackson Jaxrs providers). This worked for me:
#JsonIgnoreProperties(ignoreUnknown = true, value = { "actorsAsString",
"writersAsString", "directorsAsString", "genresAsString" })
I can only think of a non-jackson solution, to use a base class that does not have references for the mapping and then cast to the actual class:
// expect a B on an incoming request
class B {
// ...
}
// after the data is read, cast to A which will have empty references
class A extends B {
public Map<String,List<String>> references;
}
Why do you even send the References if you don't want them?
Or is the incoming data out of your hands and you just want to avoid the mapping exception telling you that jackson cannot find a property to set for incoming references? For that we use a base class which all of our Json model classes inherit:
public abstract class JsonObject {
#JsonAnySetter
public void handleUnknown(String key, Object value) {
// for us we log an error if we can't map but you can skip that
Log log = LogFactory.getLog(String.class);
log.error("Error mapping object of type: " + this.getClass().getName());
log.error("Could not map key: \"" + key + "\" and value: \"" + "\"" + value.toString() + "\"");
}
Then in the POJO you add #JsonIgnoreProperties so that incoming properties will get forwarded to handleUnknown()
#JsonIgnoreProperties
class A extends JsonObject {
// no references if you don't need them
}
edit
This SO Thread describes how to use Mixins. This might be the solution, if you want to keep your structure exactly as it is, but I have not tried it.