How can I conditionally deserialize a JSON string to a POJO field?
I receive a JSON string like so:
{
"status": "we stuck",
"data" : "someData"
}
but "someData" can be just a string "under the bridge" or can be something like "['bridge 5', 'Mandela bridge']" or "[{'incident 1' : '['bridge 1', 'bridge 2]'},{'incident 2' : ['bridge 99', 'what ever else']}]"
I want to return the json string AS IS if "data" is an array then I will map to a different Type that deals with the array
I have a java class:
class Response {
String status;
String data;
}
the other Type will have data as
ArrayList<SomeOtherType> data;
This is what i have so far
ObjectMapper mapper = new ObjectMapper();
Response rspns = mapper.readValue(<theJSONStrHere>, Response.class);
this fails when data is an array, giving me the message
can not deserialize instance of java.lang.String out of START_ARRAY token
I don't know where to go from here.
You can either use a custom deserializer as aussie said or you can just modify your working solution.
class Response {
String status;
String data;
}
class Other {
String status;
ArrayList<SomeOtherType> data;
}
ObjectMapper mapper = new ObjectMapper();
Other rspns = mapper.readValue(<theJSONStrHere>, Other.class);
This will parse the JSON String to the Other class with the ArrayList.
Now it's your turn to implement the decision of then to use
Other rspns = mapper.readValue(<theJSONStrHere>, Other.class);
or when to use
Responserspns = mapper.readValue(<theJSONStrHere>, Response.class);
Note: The above is a quick and dirty solution. It works like that but I would highly recommend to use a custom deserializer, which handles the logic of what it is and what to return.
Also keep in mind that for this to work the best you might consider building the POJO structure to multiple classes which extend a base class and then work generic.
example:
class response {
String status;
}
class simpleResponse extends response {
String data;
}
class listResponse extends response {
ArrayList<Type> data;
}
class MyDeserializer extends JSONDeserializer<E extends response> {
public E deserialize...) {
}
}
To get an actual working example read about Jackson
Dont make it complicated think simple..
There are two ways
First taking List os string/(or any other type)
private List<String> tags;
Second taking List of class (if you need more than one parameters)
List<PageLink> pagelinks;
See below case example......
public class PagesJson {
private String ln;
private int pageno;
private List<String> tags;
private List<PageLink> pagelinks;
private String error;
}
public class PageLink {
private String title= null;
private String url;
}
Now json of PagesJson class as below
{"ln":en,"count":100,"viewcount":23,"pageno":17,"tags":["Ensuring safe motherhood","pregnancy health in women","Abortion"],"pagelinks":[{"title":"Abortion","url":"http://vikaspedia.in/health/women-health"},{"title":"Acts and Rules","url":"http://vikaspedia.in/social-welfare/scheduled-tribes-welfare/acts-and-rules"},{"title":"Acts and Rules ","url":"http://vikaspedia.in/social-welfare/unorganised-sector-1/acts-and-rules"}],"error":"Parameter Validation Error"}
{"ln":en,"count":100,"viewcount":23,"pageno":17,"tags":["Ensuring safe motherhood","pregnancy health in women","Abortion"],"pagelinks":[{"title":"Abortion","url":"http://vikaspedia.in/health/women-health"},{"title":"Acts and Rules","url":"http://vikaspedia.in/social-welfare/scheduled-tribes-welfare/acts-and-rules"},{"title":"Acts and Rules ","url":"http://vikaspedia.in/social-welfare/unorganised-sector-1/acts-and-rules"}],"error":"Parameter Validation Error"}
For Mapping json to class use jackson library as below.....
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
....
.....
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
PagesJson pj = mapper.readValue(response.toString(), PagesJson.class);
You can use Custom Deserializer on a Method OR a Class using below :
extends JsonDeserializer
then
#Override
public ReturnObject deserialize(JsonParser parser, DeserializationContext ctx)
throws IOException, JsonProcessingException {
JsonToken token = parser.getCurrentToken();
if(JsonToken.START_ARRAY.equals(token)){
//TODO your JSON Array code handling
}else{
//TO DO you JSON Object Handling
}
}
Related
I have an API whose response is as follows:
{
ruleId:”123”,
ruleName:”Rule1”
}
Now I am introducing a new Api which exactly has these fields but the response should not have name as ruleId ,ruleName but as id,name:
{
id:”123”,
name:”Rule1”
}
I should change in such a way so that the previous Api response should not be impacted.
Thought to use JsonProperty /JsonGetter but it will change the previous Api response as well.
Is there any way that I can have 2 getters for the same field and then use one getter for previous Apis and other one for my purpose? (My concern is only when converting Pojo to JSON)
Can anyone help?
Since you want serialize the object differently in different cases, using jackson mix-in is preferred.
Here is example how to do that.
If your pojo looks something like this:
public class CustomPojo {
private String ruleId;
private String ruleName;
public String getRuleId() {
return ruleId;
}
public void setRuleId(String ruleId) {
this.ruleId = ruleId;
}
public String getRuleName() {
return ruleName;
}
public void setRuleName(String ruleName) {
this.ruleName = ruleName;
}
}
First, you need to create one interface (or class) like this:
import com.fasterxml.jackson.annotation.JsonProperty;
public interface CostomPojoMixin {
#JsonProperty("Id")
String getRuleId();
#JsonProperty("name")
String getRuleName();
}
This interface will be used to rename fields ruleId and ruleName during serilization.
Then when you have all this setup you can write controller method and customize ObjectMapper:
#GetMapping(value = "/test/mixin")
public String testMixin() throwsJsonProcessingException {
CostomPojo cp = new CostomPojo();
cp.setRuleId("rule");
cp.setRuleName("name");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(CustomPojo.class, CostomPojoMixin.class);
String json = objectMapper.writeValueAsString(cp);
return json;
}
This endpoint should return response like this:
{"Id":"rule","name":"name"}
I use external application which expects an Object that Serializable from me like his function:
externalFunction(Object input);
So I should give that function an input that will be correctly serialized into JSON when the method is invoked (not controlled by me).
But I don't know how data is structured since I receive input from another external application dynamically. So case like this:
1. Get data from 3rd party
2. MyApp should annotate data for Json Serialization
3. Send data to 3rd party as input
4. Response will be produced as JSON
How can I achieve this? How can I give input to the function that is correctly serialized when the function is invoked?
What I tried so far:
So first thing I try is wrap data with some Wrapper like:
public class JsonWrapper<T> implements Serializable
{
public T attributes;
public JsonWrapper( T attributes )
{
this.attributes = attributes;
}
#JsonValue
public T getAttributes( )
{
return attributes;
}
}
So I wrap data like ->
data = getFromThirdParty();
wrapped = new JsonWrapper<>(data);
externalFunction(wrapped);
But it produces a response with "attributes" field which I don't want. Also I tried to use #JsonUnwrapped public T attributes; but the result is same.
I don't want this:
{
"attributes": {
... some fields/values that I don't know, get from 3rd party
}
}
I want like this:
{
... some fields/values that I don't know, get from 3rd party
}
The #JsonUnwrapped annotation doesn't work when T is a Collection (see this answer from the Jackson's creator). But the #JsonValue annotation actually does the trick:
public class JsonWrapper<T> {
#JsonValue
private T value;
public JsonWrapper(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
If you use Lombok, you can have:
#Getter
#AllArgsConstructor
public class JsonWrapper<T> {
#JsonValue
private T value;
}
Example
Consider the following class:
#Data
#AllArgsConstructor
public class Person {
private String firstName;
private String lastName;
}
When serializing an Person instance, the following result JSON is produced:
ObjectMapper mapper = new ObjectMapper();
JsonWrapper<?> wrapper = new JsonWrapper<>(new Person("John", "Doe"));
String json = mapper.writeValueAsString(wrapper);
{"firstName":"John","lastName":"Doe"}
When serializing a list of Person instances, the following result JSON is produced:
ObjectMapper mapper = new ObjectMapper();
JsonWrapper<?> wrapper = new JsonWrapper<>(
Arrays.asList(
new Person("John", "Doe"),
new Person("Jane", "Poe")
));
String json = mapper.writeValueAsString(wrapper);
[{"firstName":"John","lastName":"Doe"},{"firstName":"Jane","lastName":"Poe"}]
I'm building REST service on Jersey and using Jackson to produce JSON from java classes of my model. Model with absolutely simple values, I think this is the most typical case. But I get strange result:
[{\"name\":\"Nick\",\"role\":\"admin\",\"age\":\"32\",\"rating\":47}]
My expecting result:
[{"name":"Nick","role":"admin","age":"32","rating":47}]
My source values of fields does NOT contains any special characters. These are simple words.
There're my Java classes.
Entity:
public class User {
private String name;
private String role;
private String age;
private Integer rating;
Class of rest service:
#ServiceConfig(contextName = "myContext")
#Path("/myrest")
public class MyRestService {
private static final String JSON_CONTENT_TYPE = MediaType.APPLICATION_JSON + ";charset=UTF-8";
#Context
protected HttpServletResponse response;
#GET
#Path("/users")
#OpenTransaction
#Produces({MediaType.APPLICATION_JSON})
public String findUsers(#QueryParam("department") String department) {
response.setContentType(JSON_CONTENT_TYPE);
PDTResponse.status(response).sendStatus(Response.Status.OK.getStatusCode());
List<User> users = new ArrayList<>();
users.add(new User("Nick", "admin", "32", 47));
String jsonInString;
ObjectMapper mapper = new ObjectMapper();
try {
jsonInString = mapper.writeValueAsString(users);
} catch (JsonProcessingException ex) {
jsonInString = "thrown exception: " + ex.getMessage();
}
return jsonInString;
}
I've tried to use annotation #JsonRawValue for string properties:
#JsonRawValue
private String name;
But result in this case was:
[{\"name\":Nick,\"role\":admin,\"age\":32,\"rating\":47}]
And I expect:
[{"name":"Nick","role":"admin","age":"32","rating":47}]
It's obvious that Jackson somehow escapes the quotes in result json of response. But why does it do it, and most importantly how to avoid that? By themselves they are just strings! Without any quotes or special characters.
I use Java 7 and Jackson 2.6.1. And Postman to test result.
Any ideas for fix of my problem?
You can configure the ObjectMapper:
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
String jsonUsers = mapper.writeValueAsString(users);
more info here
All strings in java have to escape quotes in them. So jsonInString should have slashes in it. When you output jsonInString though it shouldn't have the quotes. Are you looking at it in a debugger or something?
Do this.
ObjectMapper mapper = new ObjectMapper();
mapper.getFactory().setCharacterEscapes(new JsonUtil().new CustomCharacterEscapes());
ObjectWriter writer = mapper.writer();
String jsonDataObject = mapper.writeValueAsString(configMap);
public class CustomCharacterEscapes extends CharacterEscapes {
private final int[] _asciiEscapes;
public CustomCharacterEscapes() {
_asciiEscapes = standardAsciiEscapesForJSON();
//By default the ascii Escape table in jackson has " added as escape string
//overwriting that here.
_asciiEscapes['"'] = CharacterEscapes.ESCAPE_NONE;
}
#Override
public int[] getEscapeCodesForAscii() {
return _asciiEscapes;
}
#Override
public SerializableString getEscapeSequence(int i) {
return null;
}
}
If you are using Spring and the #ControllerAdvice for JSONP, then create a wrapper for the JSON string and use #JsonRawValue on the property. The JSONP #ControllerAdvice will not wrap a String response, it needs an Object.
public class JsonStringResponse {
#JsonValue
#JsonRawValue
private String value;
public JsonStringResponse(String value) {
this.value = value;
}
}
#GetMapping
public ResponseEntity<JsonStringResponse> getJson() {
String json = "{"id":2}";
return ResponseEntity.ok().body(new JsonStringResponse(json));
}
#ControllerAdvice
public class JsonpControllerAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpControllerAdvice() {
super("callback");
}
}
Response is a json object {"id":2}
If there is a callback parameter the response is callbackparameter({"id":2});
Looks like you are over complicating your JAX-RS resource class.
To use Jackson as a JSON provider for Jersey 2.x, you don't need to create an ObjectMapper instance like that. There's a better way to achieve it. Keep reading for more details.
Adding Jackson module dependencies
To use Jackson 2.x as your JSON provider you need to add jersey-media-json-jackson module to your pom.xml file:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.25.1</version>
</dependency>
Registering the Jackson module
Then register the JacksonFeature in your Application / ResourceConfig subclass:
#ApplicationPath("/api")
public class MyApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(JacksonFeature.class);
return classes;
}
}
#ApplicationPath("/api")
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(JacksonFeature.class);
}
}
If you don't have an Application / ResourceConfig subclass, you can register the JacksonFeature in your web.xml deployment descriptor. The specific resource, provider and feature fully-qualified class names can be provided in a comma-separated value of jersey.config.server.provider.classnames initialization parameter.
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>org.glassfish.jersey.jackson.JacksonFeature</param-value>
</init-param>
The MessageBodyWriter provided by Jackson is JacksonJsonProvider. For more details on how to use Jackson as a JSON provider, have a look at this answer. If you need to customize the ObjectMapper, refer to this answer.
Fixing your resource class
By using the approach described above, you resource class can be as simple as:
#Path("/users")
public class MyRestService {
#GET
#Produces({MediaType.APPLICATION_JSON + ";charset=UTF-8"})
public List<User> findUsers() {
List<User> users = new ArrayList<>();
users.add(new User("Nick", "admin", "32", 47));
return Response.ok(users).build();
}
When requesting such endpoint, it will give you the expected JSON as result.
I have also the same problem and tried different solutions, but non works. The problem is not with the mapper, but with the input to the mapper. As in your case:
jsonInString = mapper.writeValueAsString(users); 'users' is a collection. You need to convert each user to JSONObject, add it to JSONArray and then use the mapper on the array: like this
JSONArray users = new JSONArray();
for (Collection user : usersCollection) {
JSONObject user = new JSONObject(mapper.writeValueAsString(user));
users.put(user);
}
mapper.writeValueAsString(user));
I don't know why, but in my case it works doing this :
private static final String COOKIE_TEMPLATE = "{0}={1};Version={2};Domain={3};Max-Age={4};Path='/'";
response.addHeader("Set-Cookie", MessageFormat.format(COOKIE_TEMPLATE, cookie.getName(),cookie.getValue(), cookie.getVersion(), cookie.getDomain(),Integer.toString(cookie.getMaxAge())));
return ResponseEntity.ok(...);
cookie is a javax.servlet.http.Cookie, and cookie.getValue() contains a string produced by
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(obj);
If I use
response.addCookie(cookie)
I have a resulting cookie definition as JSON with backslashes.
But, if I use
response.addHeader("Set-Cookie",MessageFormat(TEMPLATE,cookie.get...))
I managed the same resulting cookie definition as JSON, but without backslashes.
In case of having several cookies, addHeader("Set-Cookie") only creates/updates the desired cookie. The other ones are maintained and won't be altered.
public class StateDate{
#JsonRawValue
Boolean state;
#JsonRawValue
String date;
public String toJson() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), false);
try {
return mapper.writeValueAsString(this);
} catch (com.fasterxml.jackson.core.JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
I've faced similar issue, Following configuration will help sort the issue:
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, false);
For some people who still need an answer if struggling
Try adding #JsonRawValue to the field.
The #JsonRawValue annotation can instruct Jackson to serialize a property exactly as is.
Even I came across this problem today and I stumbled across this question. People have provided multiple ways of removing the backslashes, but the thing is that the problem goes down to the very essence of what we are trying to do here.
We want to return the json response of an api call, but we are returning it as a JSONString formatted in way so that it can be printed, read and understood in Java. When you print it it looks exactly the way you want it to be when you return it.
Long story short, you must return the bytes from the function, not a String. Change the return type to byte[] and return this:
new ObjectMapper().writeValueAsString(response).getBytes(StandardCharset.UTF_8);
This will give you the purest JSON you ever want to read. Mostly, people face this issue when on the other side they are reading from an InputStream and are unable to map it to the same class and it does not work. This is how you'll fix it.
It should not be a problem, just you need to parse it in javascript and use it : JSON.parse(response)
I have a REST API specification that talks with back-end microservices, which return the following values:
On "collections" responses (e.g. GET /users) :
{
users: [
{
... // single user object data
}
],
links: [
{
... // single HATEOAS link object
}
]
}
On "single object" responses (e.g. GET /users/{userUuid}) :
{
user: {
... // {userUuid} user object}
}
}
This approach was chosen so that single responses would be extensible (for example, maybe if GET /users/{userUuid} gets an additional query parameter down the line such at ?detailedView=true we would have additional request information).
Fundamentally, I think it is an OK approach for minimizing breaking changes between API updates. However, translating this model to code is proving very arduous.
Let's say that for single responses, I have the following API model object for a single user:
public class SingleUserResource {
private MicroserviceUserModel user;
public SingleUserResource(MicroserviceUserModel user) {
this.user = user;
}
public String getName() {
return user.getName();
}
// other getters for fields we wish to expose
}
The advantage of this method is that we can expose only the fields from the internally used models for which we have public getters, but not others. Then, for collections responses I would have the following wrapper class:
public class UsersResource extends ResourceSupport {
#JsonProperty("users")
public final List<SingleUserResource> users;
public UsersResource(List<MicroserviceUserModel> users) {
// add each user as a SingleUserResource
}
}
For single object responses, we would have the following:
public class UserResource {
#JsonProperty("user")
public final SingleUserResource user;
public UserResource(SingleUserResource user) {
this.user = user;
}
}
This yields JSON responses which are formatted as per the API specification at the top of this post. The upside of this approach is that we only expose those fields that we want to expose. The heavy downside is that I have a ton of wrapper classes flying around that perform no discernible logical task aside from being read by Jackson to yield a correctly formatted response.
My questions are the following:
How can I possibly generalize this approach? Ideally, I would like to have a single BaseSingularResponse class (and maybe a BaseCollectionsResponse extends ResourceSupport class) that all my models can extend, but seeing how Jackson seems to derive the JSON keys from the object definitions, I would have to user something like Javaassist to add fields to the base response classes at Runtime - a dirty hack that I would like to stay as far away from as humanly possible.
Is there an easier way to accomplish this? Unfortunately, I may have a variable number of top-level JSON objects in the response a year from now, so I cannot use something like Jackson's SerializationConfig.Feature.WRAP_ROOT_VALUE because that wraps everything into a single root-level object (as far as I am aware).
Is there perhaps something like #JsonProperty for class-level (as opposed to just method and field level)?
There are several possibilities.
You can use a java.util.Map:
List<UserResource> userResources = new ArrayList<>();
userResources.add(new UserResource("John"));
userResources.add(new UserResource("Jane"));
userResources.add(new UserResource("Martin"));
Map<String, List<UserResource>> usersMap = new HashMap<String, List<UserResource>>();
usersMap.put("users", userResources);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(usersMap));
You can use ObjectWriter to wrap the response that you can use like below:
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
result = writer.writeValueAsString(object);
Here is a proposition for generalizing this serialization.
A class to handle simple object:
public abstract class BaseSingularResponse {
private String root;
protected BaseSingularResponse(String rootName) {
this.root = rootName;
}
public String serialize() {
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
String result = null;
try {
result = writer.writeValueAsString(this);
} catch (JsonProcessingException e) {
result = e.getMessage();
}
return result;
}
}
A class to handle collection:
public abstract class BaseCollectionsResponse<T extends Collection<?>> {
private String root;
private T collection;
protected BaseCollectionsResponse(String rootName, T aCollection) {
this.root = rootName;
this.collection = aCollection;
}
public T getCollection() {
return collection;
}
public String serialize() {
ObjectMapper mapper = new ObjectMapper();
ObjectWriter writer = mapper.writer().withRootName(root);
String result = null;
try {
result = writer.writeValueAsString(collection);
} catch (JsonProcessingException e) {
result = e.getMessage();
}
return result;
}
}
And a sample application:
public class Main {
private static class UsersResource extends BaseCollectionsResponse<ArrayList<UserResource>> {
public UsersResource() {
super("users", new ArrayList<UserResource>());
}
}
private static class UserResource extends BaseSingularResponse {
private String name;
private String id = UUID.randomUUID().toString();
public UserResource(String userName) {
super("user");
this.name = userName;
}
public String getUserName() {
return this.name;
}
public String getUserId() {
return this.id;
}
}
public static void main(String[] args) throws JsonProcessingException {
UsersResource userCollection = new UsersResource();
UserResource user1 = new UserResource("John");
UserResource user2 = new UserResource("Jane");
UserResource user3 = new UserResource("Martin");
System.out.println(user1.serialize());
userCollection.getCollection().add(user1);
userCollection.getCollection().add(user2);
userCollection.getCollection().add(user3);
System.out.println(userCollection.serialize());
}
}
You can also use the Jackson annotation #JsonTypeInfo in a class level
#JsonTypeInfo(include=As.WRAPPER_OBJECT, use=JsonTypeInfo.Id.NAME)
Personally I don't mind the additional Dto classes, you only need to create them once, and there is little to no maintenance cost. And If you need to do MockMVC tests, you will most likely need the classes to deserialize your JSON responses to verify the results.
As you probably know the Spring framework handles the serialization/deserialization of objects in the HttpMessageConverter Layer, so that is the correct place to change how objects are serialized.
If you don't need to deserialize the responses, it is possible to create a generic wrapper, and a custom HttpMessageConverter (and place it before MappingJackson2HttpMessageConverter in the message converter list). Like this:
public class JSONWrapper {
public final String name;
public final Object object;
public JSONWrapper(String name, Object object) {
this.name = name;
this.object = object;
}
}
public class JSONWrapperHttpMessageConverter extends MappingJackson2HttpMessageConverter {
#Override
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// cast is safe because this is only called when supports return true.
JSONWrapper wrapper = (JSONWrapper) object;
Map<String, Object> map = new HashMap<>();
map.put(wrapper.name, wrapper.object);
super.writeInternal(map, type, outputMessage);
}
#Override
protected boolean supports(Class<?> clazz) {
return clazz.equals(JSONWrapper.class);
}
}
You then need to register the custom HttpMessageConverter in the spring configuration which extends WebMvcConfigurerAdapter by overriding configureMessageConverters(). Be aware that doing this disables the default auto detection of converters, so you will probably have to add the default yourself (check the Spring source code for WebMvcConfigurationSupport#addDefaultHttpMessageConverters() to see defaults. if you extend WebMvcConfigurationSupport instead WebMvcConfigurerAdapter you can call addDefaultHttpMessageConverters directly (Personally I prefere using WebMvcConfigurationSupport over WebMvcConfigurerAdapter if I need to customize anything, but there are some minor implications to doing this, which you can probably read about in other articles.
Jackson doesn't have a lot of support for dynamic/variable JSON structures, so any solution that accomplishes something like this is going to be pretty hacky as you mentioned. As far as I know and from what I've seen, the standard and most common method is using wrapper classes like you are currently. The wrapper classes do add up, but if you get creative with your inheretence you may be able to find some commonalities between classes and thus reduce the amount of wrapper classes. Otherwise you might be looking at writing a custom framework.
I guess you are looking for Custom Jackson Serializer. With simple code implementation same object can be serialized in different structures
some example:
https://stackoverflow.com/a/10835504/814304
http://www.davismol.net/2015/05/18/jackson-create-and-register-a-custom-json-serializer-with-stdserializer-and-simplemodule-classes/
I am converting Java bean to JSON string using writeValueAsString method of ObjectMapper where uppercase variables from Java bean is being changed to lowercase in JSON string.
Jackson 2.7.4 version implemented.
Base bean sample -
public class BaseBean {
private static final long serialVersionUID = 3947489072259877540L;
private int _iXId;
private String _sPNR;
private ArrayList _alMinPriced = new ArrayList<TermBean>();
public int getXId() {
return _iXId;
}
public void setXId(int id) {
_iXId = id;
}
public String getPNRNumber() {
return _sPNR;
}
public void setPNRNumber(String _spnr) {
_sPNR = _spnr;
}
public ArrayList getMinPriced() {
return _alMinPriced;
}
public void setMinPriced(ArrayList minPriced) {
_alMinPriced = minPriced;
}
public void setMinPriced(TermBean bnTerm) {
_alMinPriced.add(bnTerm);
}
}
Earlier, we were using net.sf.json.JSON & JSONSerializer for Java bean to JSON conversion. And generated JSON string was having similar naming as what we are having Java bean. Due to performance issue, I want to change this & implement Jackson.
Restrictions : we can't change Java bean naming convention as these beans are from older project and there is little scope to change the variable names in bean and even adding json properties in each bean.
I have tried below code but that didn't worked
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
Also, I have tried customized PropertyNamingStrategy but not clear on this.
Edited :
net.sf.json.JSON generated JSON string as mentioned below for above bean :
{"XId":11,"PNRNumber":"123456789","minPriced":[{"name":"JSON"},{"name":"simple"}]}
Jackson generated JSON string as mentioned below for above bean :
{"xid":11,"pnrnumber":"123456789","minPriced":[{"name":"JSON"},{"name":"Jackson"}]}
As you can see that "XId" converted to "xid" in jackson and "PNRNumber" converted to "pnrnumber" in jackson.
Is there any configuration changes available in Jackson to avoid such modification.
OR How to handle such scenario.
Following jars have been used:
jackson-core-2.7.4.jar
jackson-annotations-2.7.4.jar
jackson-databind-2.7.4.jar
Step 1: Please write following Mixin as follows:
import java.util.ArrayList;
import com.fasterxml.jackson.annotation.JsonProperty;
public abstract class MixIn {
#JsonProperty("PNRNumber")
abstract String getPNRNumber();
#JsonProperty("XId")
abstract int getXId();
#JsonProperty("minPriced")
abstract ArrayList getMinPriced();
}
Step 2: Please write your Module as follows:-
import com.fasterxml.jackson.databind.module.SimpleModule;
public class MyModule extends SimpleModule{
public MyModule() {
super("ModuleName");
}
#Override
public void setupModule(SetupContext context){
context.setMixInAnnotations(BaseBean.class, MixIn.class);
}
}
Step 3: Now its time to get json String as follows:
TermBean bean1=new TermBean("JSON");
TermBean bean2=new TermBean("simple");
ArrayList list=new ArrayList();
list.add(bean1);
list.add(bean2);
BaseBean bb=new BaseBean();
bb.setXId(11);
bb.setPNRNumber("123456789");
bb.setMinPriced(list);
ObjectMapper mapper = new ObjectMapper();
Module myModule = new MyModule();
mapper.registerModule(myModule);
String jsonInString = mapper.writeValueAsString(bb);
System.out.printf( "JSON: %s", jsonInString );
Output:
JSON:
{"XId":11,"PNRNumber":"123456789","minPriced":[{"name":"JSON"},{"name":"simple"}]}
Hope this helps.
Add Json Property with required keycase. Create variable with lowercase.
public class BaseBean {
#JsonProperty("XId")
private int xId;
..
}
Hope this will help