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..
Related
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.
I have multiple Java bean classes that are associated to each other (JSON Array + JSON Object) since they have a nested structure.
There are about 10 classes. Is there a way to collectively convert these classes or at-least one by one?
I had created these classes out of a JSON data which I don't have access to right now.
So, now, what I'm looking forward is to create a dummy JSON out of those classes.
Using GSON, I tried converting one of these Bean classes however, I got an empty result. Here is one of the beans called Attachment.java.
Attachment.java
package mypackagename;
import java.io.Serializable;
public class Attachment implements Serializable{
private Payload payload;
private String type;
public Payload getPayload() {
return payload;
}
public void setPayload(Payload payload) {
this.payload = payload;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Implementation
Gson gson = new Gson();
Attachment attachment = new Attachment();
String json = gson.toJson(attachment);
Sure you got an empty result. Because your JSON object is empty. You should add data to your object and test it again as below:
Attachment attachment = new Attachment(new Payload("Test Payload"), "Test attachment");
String json = new Gson().toJson(attachment);
Log.e("Test", "Json: " + json); // result: Json: {"payload":{"test":"Test Payload"},"type":"Test attachment"}
To avoid empty object, you have to set a default value to your payload and type becaus Gson will ignore any null value.
This section of the Gson User Guide: https://sites.google.com/site/gson/gson-user-guide#TOC-Finer-Points-with-Objects
The fourth bullet point explains how null fields are handled.
I want to merge the REST PATH payload to the 'Entity' object after getting it from a database, so that only the attributes provided in payload will be updated in entity. Hence, I want to ensure that only the attributes provided as part of patch payload will be updated safely.
I am using Spring Rest Controller with Hibernate entities.
#PatchMapping(value = "/{id}")
public Resource<DepartmentPEO> update(#PathVariable Long id,
#RequestBody JSONObject payload) throws Exception
{
DepartmentPEO eo = departmentService.getRow(id);
// Have to do something to update the eo object from jsonObject.
// Some api to update eo
eo = departmentService.update(id, eo);
Resource<DepartmentPEO> resource = new Resource<>(eo);
DepartmentPEO dept = resource.getContent();
id = dept.getDeptSeq();
resource.add(
linkTo(methodOn(DepartmentsRestController.class).getRow(id))
.withSelfRel());
return resource;
}
Only the modified attributes will be sent as part of payload to server instead of sending all attributes.Resource(entity) will have nested list of objects (One-to-many). Am looking for the pool-proof solution for this use case and also believe this is common/basic for every rest api supported apps.
Pointing to any API to solve this would greatly appreciated!
Thank you
Here is a working example using Jackson's ObjectMapper and BeanUtils from Spring (since I assume you're using Spring) :
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
public class StackOverflow {
#Test
public void mergeTest() throws IOException, JSONException {
DepartmentPEO existingDepartement = existingDepartmentPEO();
JSONObject payload = new JSONObject();
payload.put("name", "newName");
DepartmentPEO result = mergeToObject(payload, existingDepartement);
assert result.getName().equals("newName");
assert result.getId().equals("1");
}
private DepartmentPEO existingDepartmentPEO() {
DepartmentPEO existingDepartement = new DepartmentPEO();
existingDepartement.setId("1");
existingDepartement.setName("oldName");
return existingDepartement;
}
private DepartmentPEO mergeToObject(JSONObject payload, DepartmentPEO object) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
DepartmentPEO updateRequest = objectMapper.readValue(payload.toString(), DepartmentPEO.class);
BeanUtils.copyProperties(updateRequest, object, "id");
return object;
}
}
Here, I transform the JSONObject into a DepartmentPEO class then I copy this object into the existing one ignoring the field id.
You may want to have a generic way to ignore null fields from the JSONObject, then you can refer to this post for instance How to ignore null values using springframework BeanUtils copyProperties?
I would advice to send directly the DepartmentPEO object into the REST method signature instead of using a JSONObject.
Regards
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;
I have
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
SessionInfo register(UserProfile profileJson){
...
}
I pass profileJson this way:
http://server/url?profileJson={"email": "mymail#gmail.com"}
but my profileJson object has all null fields. What should I do to make spring parse my json?
The solution to this is so easy and simple it will practically make you laugh, but before I even get to it, let me first emphasize that no self-respecting Java developer would ever, and I mean EVER work with JSON without utilizing the Jackson high-performance JSON library.
Jackson is not only a work horse and a defacto JSON library for Java developers, but it also provides a whole suite of API calls that makes JSON integration with Java a piece of cake (you can download Jackson at http://jackson.codehaus.org/).
Now for the answer. Assuming that you have a UserProfile pojo that looks something like this:
public class UserProfile {
private String email;
// etc...
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
// more getters and setters...
}
...then your Spring MVC method to convert a GET parameter name "profileJson" with JSON value of {"email": "mymail#gmail.com"} would look like this in your controller:
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper; // this is your lifesaver right here
//.. your controller class, blah blah blah
#RequestMapping(value="/register", method = RequestMethod.GET)
public SessionInfo register(#RequestParam("profileJson") String profileJson)
throws JsonMappingException, JsonParseException, IOException {
// now simply convert your JSON string into your UserProfile POJO
// using Jackson's ObjectMapper.readValue() method, whose first
// parameter your JSON parameter as String, and the second
// parameter is the POJO class.
UserProfile profile =
new ObjectMapper().readValue(profileJson, UserProfile.class);
System.out.println(profile.getEmail());
// rest of your code goes here.
}
Bam! You're done. I would encourage you to look through the bulk of Jackson API because, as I said, it is a lifesaver. For example, are you returning JSON from your controller at all? If so, all you need to do is include JSON in your lib, and return your POJO and Jackson will AUTOMATICALLY convert it into JSON. You can't get much easier than that. Cheers! :-)
This could be done with a custom editor, that converts the JSON into a UserProfile object:
public class UserProfileEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
ObjectMapper mapper = new ObjectMapper();
UserProfile value = null;
try {
value = new UserProfile();
JsonNode root = mapper.readTree(text);
value.setEmail(root.path("email").asText());
} catch (IOException e) {
// handle error
}
setValue(value);
}
}
This is for registering the editor in the controller class:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(UserProfile.class, new UserProfileEditor());
}
And this is how to use the editor, to unmarshall the JSONP parameter:
#RequestMapping(value = "/jsonp", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
#ResponseBody
SessionInfo register(#RequestParam("profileJson") UserProfile profileJson){
...
}
You can create your own Converter and let Spring use it automatically where appropriate:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
#Component
class JsonToUserProfileConverter implements Converter<String, UserProfile> {
private final ObjectMapper jsonMapper = new ObjectMapper();
public UserProfile convert(String source) {
return jsonMapper.readValue(source, UserProfile.class);
}
}
As you can see in the following controller method nothing special is needed:
#GetMapping
#ResponseBody
public SessionInfo register(#RequestParam UserProfile userProfile) {
...
}
Spring picks up the converter automatically if you're using component scanning and annotate the converter class with #Component.
Learn more about Spring Converter and type conversions in Spring MVC.
This does solve my immediate issue, but I'm still curious as to how you might pass in multiple JSON objects via an AJAX call.
The best way to do this is to have a wrapper object that contains the two (or multiple) objects you want to pass. You then construct your JSON object as an array of the two objects i.e.
[
{
"name" : "object1",
"prop1" : "foo",
"prop2" : "bar"
},
{
"name" : "object2",
"prop1" : "hello",
"prop2" : "world"
}
]
Then in your controller method you recieve the request body as a single object and extract the two contained objects. i.e:
#RequestMapping(value="/handlePost", method = RequestMethod.POST, consumes = { "application/json" })
public void doPost(#RequestBody WrapperObject wrapperObj) {
Object obj1 = wrapperObj.getObj1;
Object obj2 = wrapperObj.getObj2;
//Do what you want with the objects...
}
The wrapper object would look something like...
public class WrapperObject {
private Object obj1;
private Object obj2;
public Object getObj1() {
return obj1;
}
public void setObj1(Object obj1) {
this.obj1 = obj1;
}
public Object getObj2() {
return obj2;
}
public void setObj2(Object obj2) {
this.obj2 = obj2;
}
}
Just add #RequestBody annotation before this param