ObjectMapper not considering annotations - java

I have the below piece of code which would convert an annotated Java object into Json String.
try {
String jsonString = mapper.writeValueAsString(obj);
LOGGER.debug("logAsJson", jsonString);
} catch (Exception e) {
LOGGER.warn("logAsJson", "Exception in logging only. Nothing critical! ", e);
}
And my class would look something like this
public class Car {
#JsonProperty("capabilities")
private List<Capability> capability = new ArrayList<Capability>();
#JsonProperty("periodicity")
#NotNull
private Periodicity periodicity;
#JsonProperty("car_driver")
private List<carDriver> carDriver = new ArrayList<carDriver>();
}
Problem is that while creating the json string from the object, object mapper is not considering the annotations for field names.
Thanks in advance.

You might have imported the json annotation and the object mapper from different library versions. Make sure they are from the same library.
Example,
import com.fasterxml.jackson.annotation.JsonProperty;
and
import com.fasterxml.jackson.databind.ObjectMapper;

Related

How to convert a JsonNode instance to an actual pojo

At a certain point in my code, I have parse a JSON document, represented as a string, to a JsonNode, because I don't know yet the actual target pojo class type.
Now, some time later, I know the Class instance of the pojo and I want to convert this JsonNode to an actual pojo of that class (which is annotated with the proper #JsonProperty annotations). Can this be done? If so, how?
I am working with Jackson 2.10.x.
In this case you can use two methods:
treeToValue
convertValue
See below example:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.StringJoiner;
public class JsonNodeConvertApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(jsonFile);
System.out.println(mapper.treeToValue(node, Message.class));
System.out.println(mapper.convertValue(node, Message.class));
}
}
class Message {
private int id;
private String body;
// getters, setters, toString
}
Above code for JSON payload like below:
{
"id": 1,
"body": "message body"
}
prints:
Message[id=1, body='message body']
Message[id=1, body='message body']

How to not create many classes just for serialising nested JSON payload?

I've JSON message coming in from rabbitmq and has the following format:
{
messageId: 123,
content: {
id: "P123456",
status: false,
error: {
description: "Something has gone wrong",
id: 'E400'
}
}
}
As you can see, the message has a few nested objects within them.
When this message comes in, I will serialise it using Jackson. Right now, however, I have to create multiple classes just for one single message.
In the example message above, I have to create 3 classes just for serialising and transforming it into a class MainMessage, like so:
public class MainMessage {
private int messageId;
private MessageContentObject content;
// getters/setters...
}
public class MessageContentObject {
private String id;
private boolean status;
private MessageErrorObject error;
// getters/setters...
}
public class MessageErrorObject {
private String description;
private string id;
// getters/setters...
}
This feels very cumbersome because in some of the messages, the nesting can be pretty deep and I will have to create a lot of classes just for the purpose of having the JSON payload transformed into the MainMessage class object. The MessageContentObject and MessageErrorObject are mostly redundant because I will never use the classes directly anywhere else in the code. I would still the values in them through MainMessage though, for example:
#RabbitListener
public void consumeMessage(MainMessage msg) {
System.out.println(msg.getContent().getError().getDescription());
}
I'm using Spring with Spring Boot.
Is this really the only way I can do when it comes to dealing with nested JSON payloads?
First, when the message comes in you Deserialize it.
Now, if you don't want to create the whole data structure to look like your incoming JSON, you can go for a Map like this
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Collections;
import java.util.Map;
public class Message {
private final Map<String, Object> details;
#JsonCreator // Deserialize the JSON using this creator
public Message(final Map<String, Object> details) {
super();
this.details = details;
}
#JsonAnyGetter // Serialize this class using the data from the map
public Map<String, Object> getDetails() {
return Collections.unmodifiableMap(details);
}
}
In this way, you won't need to change your Message class every time your incoming JSON changes.
However, this approach is useful only when you'll not be manipulating the data too much.
if you don't want to create the whole data structure or you only need a small portion of the response recieved at a time you can use Jackson JsonNode
import com.fasterxml.jackson.databind.JsonNode;
public class Message {
private JsonNode messageDetails;
/**
* Constructor used to transform your object into jsonNode.
*
* #param messageDetailsResponse
*/
public Message(Object messageDetailsResponse) {
this.messageDetails = JsonUtils.getNode(messageDetailsResponse);
}
//Simply create getters for accessing the data you want when you want it
public String getId() {
return messageDetails.findValue("messageId").asText();
}
//You can also use it later to map a portion of response to an model class
public Content getContent(){
return JsonUtils.fromJsonNode(messageDetails.get("content"),Content.class)
}
}
The Code for JsonUtils
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
#Slf4j
final public class JsonUtils {
/**
* The Object Mapper constant to deal with JSON to/from conversion activities.
*/
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public static JsonNode getNode(Object anyObject) {
try {
return OBJECT_MAPPER.readTree(OBJECT_MAPPER.writeValueAsBytes(anyObject));
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
public static <T> T fromJsonNode(final JsonNode node, final Class<T> clazz)
throws JsonProcessingException {
return OBJECT_MAPPER.treeToValue(node, clazz);
}
Some suggestions:
Consider using Lombok to reduce the code you need to write (getters setters etc.) - will make creating classes less of a problem.
Consider using static inner classes - will mean you need less files.
If you still prefer to not write your classes down, you can deserealise to a String and traverse it using Gson or similar libraries.

Converting a RequestBody json to an Object - Spring Boot

I am a begineer in java development but has previous experience on programming languages like PHP and Python. So little confused on how to proceed on spring boot with the development.
I am developing a rest API which has the following request
{
"key":"value",
"key1":"value1",
"platform_settings":[
{"key":"value"}
]
}
What I did
I created a RestController which accepts the http request and created a function for the resource
public Share share(#RequestBody final Share share) {
LOGGER.debug("This is the request", share);
return share; //
}
Question 1 : If it was any other programming language like PHP or Python, there will be helper function which will accept the json request and convert it to object which I can easily work on.
In python it is as simple as
import json
import requests
response = requests.get(...)
json_data = json.loads(response.text)
//can work on json_data anyway I want.
But in java, I will have to create a POJO class, or have jackson/JPA entity as dependency which will map the request to a Class (Which I should predefine with the requests).
Is there any better way I can do this? For every request I make, I will have to create a Class which the request can be mapped to and I will have to define the class
Entity
package com.payunow.socialsharemodule.models;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity
public class Share {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String key;
private String key1;
private Map<String,String> platform_settings;
public Share(String name, String description,Map<String,String> platform_settings) {
this.key = key;
this.key1 = key1;
this.platform_settings = platform_settings;
}
//for JPA
public Share() {}
public String getKey() {
return key;
}
public String getKey1() {
return key1;
}
public Map<String,String> getPlatform_settings() {
return platform_settings;
}
}
For every request I make, I will have to create a class defining all its variables inside. Is this the only way to do this?
You need to have Jackson dependecy for coversion of json to java object. But spring provides it by default, so you don't have to add it explicitly.
You don't need a JPA Entity. This is needed only when you want to store the recieved data into database.
Just to recieve the request you don't have to create a separate pojo class. Look at this code
#PostMapping("/json")
public JSONObject getGeneric(#RequestBody String stringToParse){
JSONParser parser = new JSONParser();
JSONObject json = null;
try {
json = (JSONObject) parser.parse(stringToParse);
} catch (ParseException e) {
e.printStackTrace();
}
return json;
}
As you can see here it takes a string as a request and converts it into a generic JSONObject. So basically you can pass any json to this endpoint.
You CanUse ObjectMapper class it has methods like convertValue and realValue..

JsonMappingException when serializing avro generated object to json

I used avro-tools to generate java classes from avsc files, using:
java.exe -jar avro-tools-1.7.7.jar compile -string schema myfile.avsc
Then I tried to serialize such objects to json by ObjectMapper,
but always got a JsonMappingException saying "not an enum" or "not a union".
In my test I create the generated object using it's builder or constructor.
I got such exceptions for objects of different classes...
Sample Code:
ObjectMapper serializer = new ObjectMapper(); // com.fasterxml.jackson.databind
serializer.register(new JtsModule()); // com.bedatadriven.jackson.datatype.jts
...
return serializer.writeValueAsBytes(avroConvertedObject); // => JsonMappingException
I also tried many configurations using: serializer.configure(...) but still failed.
Versions: Java 1.8, jackson-datatype-jts 2.3,
jackson-core 2.6.5, jackson-databind 2.6.5, jackson-annotations 2.6.5
Any suggestions?
Thanks!
If the SCHEMA member is really the case (we don't see the full error message), then you can switch it off. I use a mixin to do it, like this:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.avro.Schema;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
public class AvroGenerTests
{
abstract class IgnoreSchemaProperty
{
// You have to use the correct package for JsonIgnore,
// fasterxml or codehaus
#JsonIgnore abstract void getSchema();
}
#Test
public void writeJson() throws IOException {
BookAvro b = BookAvro.newBuilder()
.setTitle("Wilk stepowy")
.setAuthor("Herman Hesse")
.build();
ObjectMapper om = new ObjectMapper();
om.enable(SerializationFeature.INDENT_OUTPUT);
om.addMixIn(BookAvro.class, IgnoreSchemaProperty.class);
om.writeValue(new File("plik_z_gen.json"), b);
}
}
My req'ts got changed on me and I was told I needed to convert Avro objects straight to JSON without preserving any of the meta-data. My other answer herein that specified a method convertToJsonString converts the entire Avro object to JSON so that using a de-encoder you can re-create the original Avro object as an Avro object. That isn't what my mgt. wanted anymore so I was back to the old drawing board.
As a Hail Mary pass I tried using Gson and it works to do what I now had to do. It's very simple:
Gson gson = new Gson();
String theJsonString = gson.toJson(object_ur_converting);
And you're done.
2022 Avro field names
abstract class IgnoreSchemaPropertyConfig {
// You have to use the correct package for JsonIgnore,
// fasterxml or codehaus
#JsonIgnore
abstract void getClassSchema();
#JsonIgnore
abstract void getSpecificData();
#JsonIgnore
abstract void get();
#JsonIgnore
abstract void getSchema();
}
After finding the code example at https://www.programcreek.com/java-api-examples/?api=org.apache.avro.io.JsonEncoder I wrote a method that should convert any given Avro object (they extend GenericRecord) to a Json String. Code:
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.io.EncoderFactory;
import org.apache.avro.io.JsonEncoder;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
// ... Class header etc. ...
public static <T extends GenericRecord> String convertToJsonString(T event) throws IOException {
String jsonstring = "";
try {
DatumWriter<T> writer = new GenericDatumWriter<T>(event.getSchema());
OutputStream out = new ByteArrayOutputStream();
JsonEncoder encoder = EncoderFactory.get().jsonEncoder(event.getSchema(), out);
writer.write(event, encoder);
encoder.flush();
jsonstring = out.toString();
} catch (IOException e) {
log.error("IOException occurred.", e);
throw e;
}
return jsonstring;
}
The previous post answers the question correctly. I am just adding on to the previous answer. Instead of writing it to a file I converted it to a string before sending it as body in a POST request.
public class AvroGenerateJSON
{
abstract class IgnoreSchemaProperty
{
// You have to use the correct package for JsonIgnore,
// fasterxml or codehaus
#JsonIgnore abstract void getSchema();
}
public String convertToJson() throws IOException {
BookAvro b = BookAvro.newBuilder()
.setTitle("Wilk stepowy")
.setAuthor("Herman Hesse")
.build();
ObjectMapper om = new ObjectMapper();
om.enable(SerializationFeature.INDENT_OUTPUT);
om.addMixIn(BookAvro.class, IgnoreSchemaProperty.class);
String jsonString = om.writeValueAsString(b);
return jsonString;
}
}
Agree with Shivansh's answer. To add, there might be instances where we need to use the avro-generated pojo in other classes. Under the hood, spring uses jackson library in handling this so we need to override global jackson config by adding a class
#Configuration
public class JacksonConfiguration {
public abstract IgnoreSchemaProperty {
#JsonIgnore abstract void getSchema();
}
#Bean
public ObjectMapper objectMapper() {
ObjectMapper om = new ObjectMapper();
om.addMixIn(SpecificRecordBase.class, IgnoreSchemaProperty.class);
return om;
}
}
SpecificRecordBase -if we want to ignore the schema field from all avro generated classes. In this way, we can serialize/deserialize our avro classes and use it anywhere in our application without getting the issue.

How to covert JSON field name to a Java compatible property name while doing Jackson de-serialisation?

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.

Categories

Resources