Object properties lowercased when reading as JSONObject - java

I have the following problem. I'm reading a list of records from my MySQL database with Hibernate template, and then I need to modify the structure so I'm JSONObject and JSONArray (using I guess the official library : http://www.json.org/java/). If I'm using the List as a server response, records fields are properly named (thanks to #JsonProperty annotation used). But if I'm trying to create a JSONObject out of this List element, I'm getting all my fields starting with small letter, which breaks my UI.
This is my 'Task' model used :
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.codehaus.jackson.annotate.JsonAutoDetect;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
#JsonAutoDetect
#JsonIgnoreProperties(ignoreUnknown = true)
#Entity
#Table(name="tasks")
public class Task {
#Id
#GeneratedValue
#Column(name="Id")
private int Id;
#Column(name="Name", nullable=false)
private String Name;
#JsonProperty("Id")
public int getId() {
return Id;
}
#JsonProperty("Id")
public void setId(int id) {
this.Id = id;
}
#JsonProperty("Name")
public String getName() {
return Name;
}
#JsonProperty("Name")
public void setName(String name) {
this.Name = name;
}
}
and here's the code used for getting records from the DB (stripped of all the unnecessary parts):
public List<Task> getEvents() {
DetachedCriteria criteria = DetachedCriteria.forClass(Task.class);
return hibernateTemplate.findByCriteria(criteria);
}
private static JSONArray read() throws JSONException{
List<Task> list = getEvents();
Iterator<Task> listIterator = list.iterator();
JSONArray ret = new JSONArray();
String parentId;
while(listIterator.hasNext()){
Task task = listIterator.next();
JSONObject taskJSON = new JSONObject(task);
ret.put(taskJSON);
}
}
As you can see in my server response, all fields names start with small letter :
{"id":18,"name":"Release"}
Any ideas how to override this ?

Your class is overannotated, and breaks Java code conventions.
The minimum required is as follows. Everything else you've added is done by default.
#Entity
#Table(name="tasks")
public class Task {
#Id
#GeneratedValue
#Column(name="Id")
#JsonProperty("Id")
private int id;
#Column(name="Name", nullable=false)
#JsonProperty("Name")
private String name;
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;
}
}
To serialise your class all you should need is the ObjectMapper class
String json = new ObjectMapper().writeValueAsString(getEvents());
The output of which should look like:
[{"Id":18,"Name":"Build"}, {"Id":19,"Name":"Release"}]
I would discourage using capitalised property names if possible as it goes against general code conventions.

The JSON.org API is intended for very simple serialization/deserialization, it can't do what your looking for. Having said that, the majority of your annotations are actually from Jackson, which can do what your trying to accomplish.
You already have the POJOs properly annotated for Jackson, so return a JSON string conforming to them, serialize using an ObjectMapper:
final List<Task> list = getEvents();
final ObjectMapper mapper = new ObjectMapper();
final String json = mapper.writeValueAsString(list);

Related

Deserializing complex JSON response using Jackson

I am developing my web application backend using Spring. In particular, my application manages data on soccer teams and their players.
My application interacts with a third party REST API to fetch team and player data.
As for the teams, I created a Team entity as follows:
#Data
#Table(name = "team")
#Entity
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false)
private Long id;
private String name;
private String logoUrl;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "team")
private Set<Player> players;
}
The response that comes to me from the API, however, has a particular structure and contains an array of Teams in the "response" node.
Here is the structure of the response:
{
"get":"teams",
"parameters":{
"league":"135",
"season":"2020"
},
"errors":[
],
"results":20,
"paging":{
"current":1,
"total":1
},
"response":[
{
"team":{
"id":487,
"name":"Lazio",
"country":"Italy",
"founded":1900,
"national":false,
"logo":"https:\/\/media.api-sports.io\/football\/teams\/487.png"
},
"venue":{
"id":910,
"name":"Stadio Olimpico",
"address":"Viale dei Gladiatori, 2 \/ Via del Foro Italico",
"city":"Roma",
"capacity":68530,
"surface":"grass",
"image":"https:\/\/media.api-sports.io\/football\/venues\/910.png"
}
},
{
"team":{
"id":488,
"name":"Sassuolo",
"country":"Italy",
"founded":1922,
"national":false,
"logo":"https:\/\/media.api-sports.io\/football\/teams\/488.png"
},
"venue":{
"id":935,
"name":"MAPEI Stadium - Citt\u00e0 del Tricolore",
"address":"Piazza Azzuri d&apos;Italia, 1",
"city":"Reggio nell&apos;Emilia",
"capacity":23717,
"surface":"grass",
"image":"https:\/\/media.api-sports.io\/football\/venues\/935.png"
}
},
... // Other team objects
]
}
How can I parse the answer to get a List<Team> using the Jackson library?
You should create classes for Jackson that match result structure then convert instances of those classes to your Team class. Using same class for JPA entity and for Jackson deserialization is a bad idea.
There are online services that allow generating classes like this. For example this one https://json2csharp.com/json-to-pojo generated classes like this:
// import com.fasterxml.jackson.databind.ObjectMapper; // version 2.11.1
// import com.fasterxml.jackson.annotation.JsonProperty; // version 2.11.1
/* ObjectMapper om = new ObjectMapper();
Root root = om.readValue(myJsonString), Root.class); */
public class Parameters{
public String league;
public String season;
}
public class Paging{
public int current;
public int total;
}
public class Team{
public int id;
public String name;
public String country;
public int founded;
public boolean national;
public String logo;
}
public class Venue{
public int id;
public String name;
public String address;
public String city;
public int capacity;
public String surface;
public String image;
}
public class Response{
public Team team;
public Venue venue;
}
public class Root{
public String get;
public Parameters parameters;
public List<Object> errors;
public int results;
public Paging paging;
public List<Response> response;
}
As #Sankozi said you can modelize your java pojos for json deserialization.
Then use an ObjectMapper for deserialization like :
ObjectMapper mapper = new ObjectMapper();
CollectionType javaType = mapper.getTypeFactory()
.constructCollectionType(List.class, Response.class);
List<Response> asList = mapper.readValue(jsonArray, javaType);
List<Team> teams = asList.stream()
.flatMap(response -> response.getTeam())
.collect(Collectors.toList());

There is an empty array in the http response body if I just return an array of Java Instances in Spring Boot

I return an array of Java Instances in my Spring-Boot-Get-Started project.
package com.wepay.business.resource;
import com.wepay.business.model.Good;
import com.wepay.business.repo.GoodRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
#CrossOrigin(origins = {"http://localhost:3000", "http://localhost:9000", "http://localhost:8083"})
#RestController
#RequestMapping("/api")
public class GoodResource {
#Autowired
GoodRepository repository;
#GetMapping("/getGood")
public List<Good> getAllGoods() {
List<Good> goods = new ArrayList<>();
repository.findAll().forEach(goods::add);
return goods;
}
}
package com.wepay.business.repo;
import com.wepay.business.model.Good;
import org.springframework.data.repository.CrudRepository;
public interface GoodRepository extends CrudRepository<Good, Long> {
}
package com.wepay.business.model;
import javax.persistence.*;
#Entity
#Table(name = "good")
public class Good {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "name")
private String name;
#Column(name = "price")
private double price;
#Column(name = "img")
private String img;
#Column(name = "info")
private String info;
#Column(name = "amount")
private int amount;
#Column(name = "address")
private String address;
#Column(name = "soldAmount")
private String soldAmount;
#Column(name = "sellerId")
private String sellerId;
public Good(){
}
public Good(String name, Double price, String info, int amount) {
this.name = name;
this.price = price;
this.info = info;
this.amount = amount;
}
public Good(Long id, String goodName, Double unitPrice, String goodInfo, int amount) {
this(goodName, unitPrice, goodInfo, amount);
this.id = id;
}
public void setId(Long id) {
this.id = id;
}
}
The value of goods is an array of Java Instacnes
But there is only an empty array in the http response body.
I guess that I should return an array of JSON objects rather than Java Instances.
Do I need to convert the Java Instances to JSON objects? If so, is there any framework to help us to do this job?
I have been blocked by this issue since last week. Thanks in advance.
The problem resides in the fact that your Good class has no getters (atleast by what I see in your post). Add the getters and this should work.
I think you can use JpaRepository<T, ID> instead of CrudRepository<T, ID> so in this case there's no need to instantiate another List<Good>, because the repository.findAll() already returns List<Good> inside the JpaRepository, although by the way you're doing, it should also work normally.
Do I need to convert the Java Instances to JSON objects? If so, is there any framework to help us to do this job?
No. Spring already do it for you by using Jackson's serializer.
try return repository.findAll();
If there is no specific reason to use CrudRepository you can change it to JpaRepository
By doing this you can avoid conversion of Iterator to List and use like this.
public interface GoodRepository extends JpaRepository<Good, Long> {
}
// Controller
#GetMapping("/getGood")
public List<Good> getAllGoods() {
return repository.findAll();
}
Also, Make sure Getter Setter is in place for each persistable field.

Duplicate Values while using ObjectMapper of Jackson

My Bean class is as below. When the mapping happens, the JSON object contains duplicate values.
Response:
{"Id":"00PJ0000003mOgMMAU","Name":"web.xml","name":"web.xml","id":"00PJ0000003mOgMMAU"}
Why the values are getting duplicated?
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
#JsonIgnoreProperties(ignoreUnknown = true)
public class AttachmentsMapper
{
#JsonProperty(value = "Id")
private String Id;
#JsonProperty(value = "Name")
private String Name;
public String getId() {
return Id;
}
public void setId(String Id) {
this.Id = Id;
}
public String getName() {
return Name;
}
public void setName(String Name) {
this.Name = Name;
}
}
It doesn't print duplicate the same field twice it prints 2 different fields that it finds. Jackson sees you want to print "name" because you have a getter called getName() and "Name" because you have annotated the Name field as #JsonProperty with a different key. It sees different fields because "name" != "Name". Two solutions :
Move the annotation to the getter. The field is ignored by default because it's private E.g.
#JsonProperty(value = "Name")
public String getName() {
return Name;
}
Use a more recent version of Jackson as you seem to be using 1.8 from com.codehaus. Use 1.9 from there or even better use the latest from com.fasterxml. I tried your code as it is with 1.9 and it worked without moving the annotation.
In Jackson 2 try to disable Jackson visibility for all the sources (getters, setters, fields, etc.) and then just enable the visibility for the object fields:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
...
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

How to serialize ObjectId to JSON?

I want to serialize ObjectId of my Product class to JSON. I got the following JSON:
[{"name":"Play for Java: Covers Play 2","type":"Book","company":"Manning Publications","price":30.0,"imagePath":"public/images/play-for-java.png","rating":4.5,"category":"Computer","author":"Nicolas Leroux","publicationDate":1396224000000,"numPage":320,"_id":539da7a6370882f10d5c2777}]
You can notice that the "_id" didn't be properly serialized, it should be "539da7a6370882f10d5c2777" (with double quotes) and not just 539da7a6370882f10d5c2777.
Therefore, I have tried to implement my own ObjectIdSerializer as following:
import java.io.IOException;
import org.bson.types.ObjectId;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class ObjectIdSerializer extends JsonSerializer<ObjectId> {
#Override
public void serialize(ObjectId value, JsonGenerator jsonGen,SerializerProvider provider) throws IOException,
JsonProcessingException {
jsonGen.writeString(value.toString());
}
}
It gave me the different error: java.lang.String cannot be cast to org.bson.types.ObjectId (through reference chain: models.Book["_id"])
Here are my Product class and Book class:
Product.java
#JsonTypeInfo(use= JsonTypeInfo.Id.CLASS,property="_class")
public class Product {
#ObjectId #Id
#JsonSerialize(using = ObjectIdSerializer.class)
protected String id;
#JsonProperty("name")
protected String name;
#JsonProperty("type")
protected String type;
#JsonProperty("description")
protected String description;
#JsonProperty("company")
protected String company;
#JsonProperty("price")
protected float price;
#JsonProperty("imagePath")
protected String imagePath;
#JsonProperty("imageName")
protected String imageName;
#JsonProperty("rating")
protected float rating;
public Product() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
// Getters and setters
...
Book.java
public class Book extends Product{
#JsonProperty("category")
private String category;
#JsonProperty("author")
private String author;
#JsonProperty("publicationDate")
private Date publicationDate;
#JsonProperty("numPage")
private int numPage;
public Book() {
}
// Getters and setters
...
Can you help me figure it out how can I properly serialize the ObjectId to JSON?
It looks like Jackson has been customized to serialize the string id field in a special way. That is probably a part of the integration with org.bson library.
The problem is that your deserializer is parametrized by the ObjectId type instead of String or plain Object. Try to change it as follows and also remove the #ObjectId annotation from the field declaration. Here is an example:
public class ObjectIdSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object value, JsonGenerator jsonGen,SerializerProvider provider) throws IOException {
jsonGen.writeString(value.toString());
}
}
You may also consider adopting the Jackson-Jongo provider class to fix the object id serialization for all the classes.

How to define index by several columns in hibernate entity?

Morning.
I need to add indexing in hibernate entity. As I know it is possible to do using #Index annotation to specify index for separate column but I need an index for several fields of entity.
I've googled and found jboss annotation #Table, that allows to do this (by specification). But (I don't know why) this functionality doesn't work. May be jboss version is lower than necessary, or maybe I don't understant how to use this annotation, but... complex index is not created.
Why index may not be created?
jboss version 4.2.3.GA
Entity example:
package somepackage;
import org.hibernate.annotations.Index;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#Entity
#org.hibernate.annotations.Table(appliesTo = House.TABLE_NAME,
indexes = {
#Index(name = "IDX_XDN_DFN",
columnNames = {House.XDN, House.DFN}
)
}
)
public class House {
public final static String TABLE_NAME = "house";
public final static String XDN = "xdn";
public final static String DFN = "dfn";
#Id
#GeneratedValue
private long Id;
#Column(name = XDN)
private long xdn;
#Column(name = DFN)
private long dfn;
#Column
private String address;
public long getId() {
return Id;
}
public void setId(long id) {
this.Id = id;
}
public long getXdn() {
return xdn;
}
public void setXdn(long xdn) {
this.xdn = xdn;
}
public long getDfn() {
return dfn;
}
public void setDfn(long dfn) {
this.dfn = dfn;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
When jboss/hibernate tries to create table "house" it throws following exception:
Reason: org.hibernate.AnnotationException: #org.hibernate.annotations.Table references an unknown table: house
Please try the following:
#Entity
#org.hibernate.annotations.Table(appliesTo = House.TABLE_NAME,
indexes = {
#Index(name = "IDX_XDN_DFN",
columnNames = {House.XDN, House.DFN}
)
}
)
#Table(name="house")
public class House {
...
}
Note that this should also allow you to create a multi-column index (based on the index name):
#Index(name = "index1")
public String getFoo();
#Index(name = "index1")
public String getBar();
P.S.: What version of Hibernate are you using BTW? What database/dialect?
You have to have hibernate.hbm2ddl.auto set to create in persistence.xml. When set to update hibernate won't create indexes.
hibernate.hbm2ddl.auto = create
You'd better go with a composite primary key.
This article explains how to do it with JPA annotations. It uses #Embeddable and #EmbeddedId

Categories

Resources