I have 2 services running(Frontend, Backend). Frontend service has Feign Client to connect to Backend API service. When a request hits Frontend, through Feign client it hits Backend API.
I have an API endpoint GET /api/v1/person which returns response like below format
{
"firstName": "stack"
"lastName": "overflow",
"address" : {
"address1" : "xyz",
"address2" : "abc street",
"postalcode": "123456"
}
}
The data for the address object is populated from external API as a JSON string. But the keys are in a different format, so I am using #JsonProperty annotation in setters and getters to convert them properly.
class Person {
private String firstName;
private String lastName;
private Address address;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setAddress(Address address) {
this.address= address;
}
public void getAddress() {
return this.address;
}
}
public class Address {
private String address1;
private String address2;
private String postalCode;
#JsonProperty("address1")
public String getAddress1() {
return address1;
}
#JsonProperty("ADD_ONE")
public void setAddress1(String address1) {
this.address1 = address1;
}
#JsonProperty("address2")
public String getAddress2() {
return address2;
}
#JsonProperty("ADD_TWO")
public void setAddress2(String address2) {
this.address2 = address2;
}
#JsonProperty("postalCode")
public String getPostalCode() {
return postalCode;
}
#JsonProperty("PST_CDE")
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
}
From the Backend API, the response looks good and as expected. But when it reached Frontend service, the address object returned as null/empty as below
{
"firstName": "stack"
"lastName": "overflow",
"address" : {}
}
Instead of using #JsonProperty in the Address class, if I rename the JSON keys and convert the JSON string to a java object, I am getting the expected response.
I am not sure why it returns empty or null when I use the #JsonProperty in the Address class and pass the response from the Backend to the Frontend service.
Note: Response object is common for both services.
Can someone please let me know, what I am missing here?
Thanks.
You should also add address attribute in your Person class, along with its getters and setters. I believe that you must be having an address class with the following attributes :
address1
address2
postalcode
Look at the code sample below.
class Person {
private String firstName;
private String lastName;
private Address address;
#JsonProperty("firstName")
public String getFirstName() {
return firstName;
}
#JsonProperty("FST_NME")
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#JsonProperty("lastName")
public String getLastName() {
return lastName;
}
#JsonProperty("LST_NME")
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setAddress(Address address) {
this.address= address;
}
public void getAddress() {
return this.address;
}
}
The issue is resolved when I added a new Address class for the Frontend service.
Frontend Service - with no annotations set.
public class Address {
private String address1;
private String address2;
private String postalCode;
public String getAddress1() {
return address1;
}
public void setAddress1(String address1) {
this.address1 = address1;
}
public String getAddress2() {
return address2;
}
public void setAddress2(String address2) {
this.address2 = address2;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
}
Backend Service - with #JsonProperty for setters and getters to convert the JSON keys.
public class Address {
private String address1;
private String address2;
private String postalCode;
#JsonProperty("address1")
public String getAddress1() {
return address1;
}
#JsonProperty("ADD_ONE")
public void setAddress1(String address1) {
this.address1 = address1;
}
#JsonProperty("address2")
public String getAddress2() {
return address2;
}
#JsonProperty("ADD_TWO")
public void setAddress2(String address2) {
this.address2 = address2;
}
#JsonProperty("postalCode")
public String getPostalCode() {
return postalCode;
}
#JsonProperty("PST_CDE")
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
}
Since the Address class was Common for both the services, the issue occurred, where the Frontend service was not able to convert the object since the Address class attribute names are different(used #JsonProperty for setter method).
Please comment if there is anything that I missed.
Thanks all for your proper responses.
Related
I am using SpringBoot and trying to deserialize JSON like:
{
"userId": "Dave",
"queryResults": {
"id": "ABC",
"carData": {.....},
"carId": "Honda",
"status": 0,
"model": "X"
}
}
, into MyRequestModel clas:
public class MyRequestModel {
private String userId;
private String: queryResults;
}
, that is received as #RequestBody parameter in my #PostMapping method that looks like:
#PostMapping
public String postDate(#RequestBody MyRequestModel data) {
...
return "posted";
}
The above queryResults field is supposed to be stored as a CLOB in a database.
Problem I am having is that if I send this JSON to hit my endpoint (PostMapping) method, it cannot deserialize it into MyRequestModel and I get this error:
Cannot deserialize instance of java.lang.String out of START_OBJECT token
at [Source: (PushbackInputStream); line: 3, column: 18] (through reference chain: MyRequestModel["queryResults"])]
I guess the real answer to your question is: if you NEED the queryResults property to be a String, then implement a custom deserializer.
If not, then, use one of the alternatives that Jonatan and Montaser proposed in the other answers.
Implementing a custom deserializer within Spring Boot is fairly straightforward, since Jackson is its default serializer / deserializer and it provides a easy way to write our own deserializer.
First, create a class that implements the StdDeserializer<T>:
MyRequestModelDeserializer.java
public class MyRequestModelDeserializer extends StdDeserializer<MyRequestModel> {
public MyRequestModelDeserializer() {
this(null);
}
public MyRequestModelDeserializer(Class<?> vc) {
super(vc);
}
#Override
public MyRequestModel deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = p.getCodec().readTree(p);
String userId = node.get("userId").asText();
String queryResults = node.get("queryResults").toString();
MyRequestModel model = new MyRequestModel();
model.setQueryResults(queryResults);
model.setUserId(userId);
return model;
}
}
Second, mark your class to be deserialized using your custom deserializer by using the #JsonDeserialize annotation:
MyRequestModel.java
#JsonDeserialize(using = MyRequestModelDeserializer.class)
public class MyRequestModel {
private String userId;
private String queryResults;
}
It's done.
queryResults is a String on Java side but it is an Object on JSON side.
You will be able to deserialize it if you send it in as a String:
{
"userId": "Dave",
"queryResults": "foo"
}
or if you create classes that maps to the fields:
public class MyRequestModel {
private String userId;
private QueryResults queryResults;
}
public class QueryResults {
private String id;
private CarData carData;
private String carId;
private Integer status;
private String model;
}
or if you serialize it into something generic (not recommended):
public class MyRequestModel {
private String userId;
private Object queryResults;
}
public class MyRequestModel {
private String userId;
private Map<String, Object> queryResults;
}
public class MyRequestModel {
private String userId;
private JsonNode queryResults;
}
You have two options to deserialize this request:-
change the type of queryResults to Map<String, Object>, it will accepts everything as an object of key and value. (Not recommended)
public class MyRequestModel {
private String userId;
private Map<String, Object> queryResults;
}
You have to create a class that wraps the results of queryResults as an object.
class QueryResult {
private String id;
private Map<String, Object> carData;
private String carId;
private Integer status;
private String model;
public QueryResult() {}
public QueryResult(String id, Map<String, Object> carData, String carId, Integer status, String model) {
this.id = id;
this.carData = carData;
this.carId = carId;
this.status = status;
this.model = model;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Map<String, Object> getCarData() {
return carData;
}
public void setCarData(Map<String, Object> carData) {
this.carData = carData;
}
public String getCarId() {
return carId;
}
public void setCarId(String carId) {
this.carId = carId;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}
and make the type of queryResult as shown:-
public class MyRequestModel {
private String userId;
private QueryResult queryResults;
}
Customer entity
#Entity
#Table(name="Customer")
public class Customer {
#Id
#GeneratedValue
private Long cusId;
#Column(name="firstname")
private String firstName;
#Column(name="lastname")
private String lastName;
#Column(name="address")
private String add;
#Column(name="tel")
private int tel;
#Column(name="email")
private String email;
#Column(name="userName")
private String username;
#Column(name="password")
private String password;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "customer")
#JsonManagedReference
private List<Complain> complain=new ArrayList<Complain>();
public Customer() {
super();
// TODO Auto-generated constructor stub
}
public Long getCusId() {
return cusId;
}
public void setCusId(Long cusId) {
this.cusId = cusId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
public int getTel() {
return tel;
}
public void setTel(int tel) {
this.tel = tel;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<Complain> getComplain() {
return complain;
}
public void setComplain(List<Complain> complain) {
this.complain = complain;
}
}
Complain Entity
#Entity
#Table(name="Complain")
public class Complain {
#Id
#GeneratedValue
private Long id;
#Column(name="repfirst")
private String repfirst;
#Column(name="replast")
private String replast;
#Column(name="warranty")
private String warranty;
#Column(name="dop")
private String purDate;
#Column(name="Nomachine")
private String Nomachine;
#Column(name="Complain")
private String Complain;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="cus_id", referencedColumnName = "cusId")
#JsonBackReference
private Customer customer;
public Complain() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRepfirst() {
return repfirst;
}
public void setRepfirst(String repfirst) {
this.repfirst = repfirst;
}
public String getReplast() {
return replast;
}
public void setReplast(String replast) {
this.replast = replast;
}
public String getWarranty() {
return warranty;
}
public void setWarranty(String warranty) {
this.warranty = warranty;
}
public String getPurDate() {
return purDate;
}
public void setPurDate(String purDate) {
this.purDate = purDate;
}
public String getNomachine() {
return Nomachine;
}
public void setNomachine(String nomachine) {
Nomachine = nomachine;
}
public String getComplain() {
return Complain;
}
public void setComplain(String complain) {
Complain = complain;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
I want to pass data to the mysql database through the postman tool. How can I pass value for the foreign key column through the json query?
This is the method I have used in controller
#PostMapping(path="/",consumes="application/json",produces="application/json")
public void addComplain(#RequestBody Complain complain)
{
Integer id = complainService.getAllComplain().size()+1;
complain.setId(new Long(id));
complainService.createOrUpdateComplain(complain); }
After I send the request from the postman, all the data are saved except the foreign key. I think I missed something in mapping two tables.
This is my json query
{"complain":"No power",
"repfirst":"hi",
"replast":"all",
"warranty":"yes",
"purDate":"2020-02-29",
"nomachine":"6",
"tel":"46544654",
"Customer":[
{"cusId":"1"}
]
}
Database table image
Please help, Thanks in advance
{
"repfirst": "",
"replast": "",
"warranty": "",
"purDate" : "",
"Nomachine": "",
"Complain": "",
"cus_id": 1
}
That should work
"Customer":[
{"cusId":"1"}
]
This would work if you had Set<Customer> customers converted to an array in json, for example.
I am new to springboot, i am getting a response as below in my json response:
"Number": "08002050"
I have defined it as String in my spring boot app.
I want to get a response as below:
"Number": 08002050
How do i accomplish this. please help
You can manage it in server side with a tricky way.
public class User {
private int id;
private String name;
#JsonIgnore // ignore this field when serialize
private String number;
#JsonProperty(value = "number") // change name of field when serialize
private int intValueOfNumber;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public int getIntValueOfNumber() {
return Integer.parseInt(number); // parse number string to int
}
public void setIntValueOfNumber(int intValueOfNumber) {
this.intValueOfNumber = intValueOfNumber;
}
}
In this entity #JsonIgnore annotation is ignore your field for JSON serialization and pass intValueOfNumber as int to JSON. Your json will be following:
{"id":1,"name":"Java","number":44124}
You may lost zero suffix of number string when you parse it to int.
Below is my JSON file :
Input JSON:
{
"name": "Tamiliniyan",
"address": {
"street": "My street",
"city": "Texas"
}
}
Controller class:
#RestController
#RequestMapping(value = "/customer")
public class CustomerController {
#Autowired
private WelcomeService customerService;
#RequestMapping(method = RequestMethod.POST)
public void addCustomer(#RequestBody Customer customer) {
return customerService.addTranslation(customer);
}
}
POJO:
public class Customer
{
private Address address;
private String name;
public Address getAddress ()
{
return address;
}
public void setAddress (Address address)
{
this.address = address;
}
public String getName ()
{
return name;
}
public void setName (String name)
{
this.name = name;
}
}
public class Address
{
private String street;
private String city;
public String getStreet ()
{
return street;
}
public void setStreet (String street)
{
this.street = street;
}
public String getCity ()
{
return city;
}
public void setCity (String city)
{
this.city = city;
}
}
Now I have to dynamically add city or zipcode. How to do it? Basically Client system can pass any new additional JSON field with current structure (like city or zipcode). CustomerController class should able to parse it. What is better approach to handle dynamic JSON element in restful services?
In my opinion, the easiest and most performant way of handling JSON within Java using Spring when you don't know the final structure of your JSON is to use Map(s).
You could add something like this to your POJO:
public class Customer {
private Address address;
private String name;
private Map<String, ?> additionalFields;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, ?> getAdditionalFields() {
return additionalFields;
}
public void setAdditionalFields(Map<String, ?> additionalFields) {
this.additionalFields = additionalFields;
}
}
If you then post something like this:
{
"name": "Tamiliniyan",
"address": {
"street": "My street",
"city": "Texas"
},
"additionalFields": {
"nested1":{
"zip-code": "00055"
}
}
}
This is what you get when Spring processes it:
In order to retrieve data you could then use methods like:
customer.getAdditionalFields().containsKey("nested1")
customer.getAdditionalFields().get("nested1")
Another approach would be to add whatever fields you need to your Class and then ignore empty fields in you Jackson configuration
What am I doing wrong that the locations property isn't being deserialized?
JSON fragment from pollinglocation.googleapis.com
...
"locations":[[{"dataset_id":841,
"election_id":2,
"id":"841:100001351",
"address":{"location_name":"ROBERT S PAYNE SCHOOL",
"line1":"1201 Floyd St",
"city":"Lynchburg",
"state":"VA",
"zip":"245011913"},
"directions":"",
"polling_hours":"6 AM - 7 PM"}]],
...
Java Object
public class PollingLocation {
public PollingLocation() {
}
String status;
ArrayList<ArrayList<Anonymous>> locations;
public class Anonymous {
public Anonymous() {
}
String dataset_id;
String election_id;
String id;
Address address;
}
public class Address {
public Address() {
}
String location_name;
String line1;
String city;
String stage;
String zip;
String polling_hours;
}
}
Code that deserializes
PollingLocation obj2 = gson.fromJson(json, PollingLocation.class);
Everything.
Specifically, you can't use inner classes or collection classes.
public class PollingLocation {
public PollingLocation() {
}
String status;
Anonymous[][] locations;
}
class Anonymous {
public Anonymous() {
}
String dataset_id;
String election_id;
String id;
Address address;
}
class Address {
public Address() {
}
String location_name;
String line1;
String city;
String stage;
String zip;
String polling_hours;
}