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'Italia, 1",
"city":"Reggio nell'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());
Related
So, I am trying my hands-on MongoDB CRUD operations using spring-data-mongodb. Below are my model classes,
#Document(collection = "alumni_students")
public class AlumniStudent {
#Id
private String id;
private String firstName;
private String lastName;
private String email;
#DBRef
private AlumniDepartment alumniDepartment;
#DBRef
private List<AlumniSubject> alumniSubjects;
... getters/setters
#Document(collection = "alumni_department")
public class AlumniDepartment {
#Id
private String id;
private String departmentName;
private String location;
... getters/setters
#Document(collection = "alumni_subjects")
public class AlumniSubject {
#Id
private String id;
private String subjectName;
private int marks;
... getters/setters
I am using MongoRepository for individual collections for their operations like below,
#Repository
public interface AlumniStudentRepository extends MongoRepository<AlumniStudent, String> { }
#Repository
public interface AlumniDepartmentRepository extends MongoRepository<AlumniDepartment, String> {}
#Repository
public interface AlumniSubjectRepository extends MongoRepository<AlumniSubject, String> {}
I have so far done good while creation and getting the student details. The issue I am facing is while updating the student data. In that also specifically while updating the data, I am confused as hell.
Below is my update code from service layer,
#Autowired
AlumniStudentRepository alumniStudentRepo;
#Autowired
AlumniDepartmentRepository alumniDeptRepo;
#Autowired
AlumniSubjectRepository alumniSubjRepo;
public AlumniStudent updateStudent(AlumniStudent student, String id) {
Optional<AlumniStudent> fetchedStudent = alumniStudentRepo.findById(id);
**// UPDATE STUDENT DATA, WORKS FINE**
if (fetchedStudent.isPresent()) {
AlumniStudent studentFromDB = fetchedStudent.get();
studentFromDB.setFirstName(student.getFirstName());
studentFromDB.setLastName(student.getLastName());
studentFromDB.setEmail(student.getEmail());
**// UPDATE DEPARTMENT DATA, WORKS FINE**
if (student.getAlumniDepartment() != null) {
Optional<AlumniDepartment> deptData = alumniDeptRepo.findById(studentFromDB.getAlumniDepartment().getId());
if (deptData.isPresent()) {
AlumniDepartment alumniDepartment = deptData.get();
alumniDepartment.setDepartmentName(student.getAlumniDepartment().getDepartmentName());
alumniDepartment.setLocation(student.getAlumniDepartment().getLocation());
alumniDeptRepo.save(alumniDepartment);
studentFromDB.setAlumniDepartment(alumniDepartment);
}
}
**// UPDATE SUBJECTS ARRAY DATA.... HOW TO DO THIS?**
if (student.getAlumniSubjects() != null && !student.getAlumniSubjects().isEmpty()) {
// Problematic area. How to perform update of arraylist here?
}
return alumniStudentRepo.save(studentFromDB);
}
}
This is the URL to hit in postman :
localhost:8080/alumnistudents/60aa384ffbf1851f56c71bef
And this is the request body:
{
"firstName": "Babita",
"lastName": "Raman",
"email": "babita#gmail.com",
"alumniDepartment": {
"departmentName": "Android Developer",
"location": "Dubai"
},
"alumniSubjects": [
{
"subjectName": "Java",
"marks": 80
},
{
"subjectName": "Unit testing",
"marks": 60
},
{
"subjectName": "Docker",
"marks": 80
}
]
}
I tried some random code but ended up with
Cannot create a reference to an object with a NULL id.
Can someone help here with how to update the arrays data which is referenced as #DbRef ?
Thanks in advance everyone.
I am trying to obtain a value from a parent node using Jackson.
I know this is possible to achieve with custom deserialisers, but then there is too much boilerplate because you suddenly have to handle everything manually.
It sounds like something quite simple but didn't find a way to do it.
To illustrate what I want - If we have a simple User with Address...
#JsonDeserialize(builder = User.Builder.class)
public class User
{
private long id;
private String firstName;
private Address address;
...
public static class Builder
{
public Builder withId(long id);
public Builder withFirstName(String value);
public Builder withAddress(Address address);
public User create();
}
}
If we have the same for address
#JsonDeserialize(builder = Address.Builder.class)
public class Address
{
...
public static class Builder
{
public Builder withUserId(long id); // is there a way to ask for the parent id here?
public Builder withStreetName(String value);
public Address create();
}
}
Sample input:
{
"id": 7,
"firstName" : "John",
"lastName" : "Smith",
"address" : {
"streetName": "1 str"
}
}
No, I don't think you can with any of the existing Jackson code. The only thing I believe that can cross parent/child relationships like that is type serialization/deserialization and the UNWRAP_ROOT_VALUE support.
If you want something like that, you'd either need to use a custom deserializer for User, or customize the User constructor to build a new address with the correct UserId before adding it to the builder's internal state. Here's an example (using Lombok to handle the boilerplate generation of builders):
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;
import lombok.experimental.Wither;
public class Scratch {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
String json = "{\"id\":1234,\"address\":{\"street\":\"123 Main St.\"}}";
User user = mapper.readValue(json, User.class);
System.out.println(user.toString());
}
#Value
#JsonDeserialize(builder = User.UserBuilder.class)
public static class User {
private final int id;
private final Address address;
#Builder(toBuilder = true)
public User(int id, Address address) {
this.id = id;
// Build a new address with the user's ID
this.address = address.withUserId(id);
}
#JsonPOJOBuilder(withPrefix = "")
public static class UserBuilder {}
}
#Value
#Builder(toBuilder = true)
#JsonDeserialize(builder = Address.AddressBuilder.class)
public static class Address {
#Wither
private final int userId;
private final String street;
#JsonPOJOBuilder(withPrefix = "")
public static class AddressBuilder {}
}
}
This consumes the following json:
{
"id": 1234,
"address": {
"street": "123 Main St."
}
}
and produces the following output:
Scratch.User(id=1234, address=Scratch.Address(userId=1234, street=123 Main St.))
I'm a newbie coder having just finished a 6 month coding crash-course. I'm working on a java webapp to demonstrate my skills, and the project idea I had involves retrieving JSON data from an API, something we didn't learn about in class. I made POJOs to match the JSON, and I'm trying to parse the JSON into java objects to store in a database, however my database tables are never filled with data when I run through the app. I suspect the problem is somewhere with my method to convert the JSON but any feedback is greatly appreciated. Here's all my code I think is relevant, sorry if its TMI. I also apologize if my code is ugly, I'm a beginner... Thanks!
API returns JSON like this:
{
"result":{
"status":1,
"num_results":1,
"total_results":500,
"results_remaining":499,
"matches":[{
"match_id":3188095188,
"match_seq_num":2784956606,
"start_time":1495079320,
"lobby_type":7,
"radiant_team_id":0,
"dire_team_id":0,
"players":[{
"account_id":86920222,
"player_slot":0,
"hero_id":18
},{
"account_id":61122568,
"player_slot":1,
"hero_id":85
},{
"account_id":10208661,
"player_slot":2,
"hero_id":13
},{
"account_id":106083675,
"player_slot":132,
"hero_id":50
}]
}]
}
}
My POJOs:
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Result {
#JsonIgnore
#Id
#GeneratedValue
private int id;
#JsonProperty("status")
private int status;
#JsonProperty("num_results")
private int num_results;
#JsonProperty("total_results")
private int total_results;
#JsonProperty("results_remaining")
private int results_remaining;
#OneToMany
#JoinColumn(name = "result_id")
#ElementCollection(targetClass=Matches.class)
#JsonProperty("matches")
private List<Matches> matches;
// getters and setters
}
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Matches {
#Id
#JsonProperty("match_id")
private int match_id;
#JsonIgnore
#ManyToOne
private Result result;
#JsonProperty("match_seq_num")
private int match_seq_num;
#JsonProperty("start_time")
private int start_time;
#JsonProperty("lobby_type")
private int lobby_type;
#JsonProperty("radiant_team_id")
private int radiant_team_id;
#JsonProperty("dire_team_id")
private int dire_team_id;
#OneToMany
#JoinColumn(name = "Matches_id")
#ElementCollection(targetClass=Players.class)
#JsonProperty("players")
private List<Players> players;
// getters and setters
}
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Players {
#JsonIgnore
#Id
#GeneratedValue
private int id;
#JsonIgnore
#ManyToOne
private Matches matches;
#JsonProperty("account_id")
private int account_id;
#JsonProperty("player_slot")
private int player_slot;
#JsonProperty("hero_id")
private int hero_id;
// getters and setters
}
Services method to read and convert the JSON to objects (url is censored, don't want my API key to be public)
public class SteamService {
public static Result getMatchHistory(String steamid){
Result result = new Result();
String MatchHistoryUrl = "https:**URL**="+steamid;
RestTemplate restTemplate = new RestTemplate();
Result jsonresult = restTemplate.getForObject(MatchHistoryUrl, Result.class);
return jsonresult;
}
}
Controller
#Controller
#RequestMapping("")
public class HomeController {
#Autowired
private ResultsDao resultsDao;
#RequestMapping(value = "", method = RequestMethod.GET)
public String index(Model model){
model.addAttribute("title", "Welcome");
return "home/home";
}
#RequestMapping(value = "", method = RequestMethod.POST)
public String processSteamIdField(#RequestParam("steamid")String steamid, Model model) {
Result newresult = getMatchHistory(steamid);
resultsDao.save(newresult);
return "redirect:results";
}
}
DAO
#Repository
#Transactional
public interface ResultsDao extends CrudRepository<Result, Integer>{
}
Maybe my approach is a bit naive, but... If you want to store the JSON as string in the database, then I would use an object mapper for this:
new ObjectMapper().writeValueAsString(myObject);
and for reading a JSON and parsing it to a class I would do:
new ObjectMapper().readValue(JSON_STRING_HERE, "utf-8"), MyPOJO.class);
Also, if you already are using Spring, then your controller may look like this (for a POST, for example)
#RequestMapping(value = "/", method = RequestMethod.POST)
public MyPojo myController(#RequestBody MyPojo myBody) {
myRepository.save(myBody);
}
So, the parsing of the JSON that the client is sending to your app and your controller is already handled by Spring
first, sorry about my bad english;
Second, I have the following Code:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class UserAccount implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private List<Venda> vendas;
}
And the following:
public class Venda implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private UserAccount cliente;
}
So, everything is okay and get the json from serialize on this way (when I ask for an UserAccount):
[
{
"id": 1,
"vendas": [
{
"id": 1,
"cliente": 1,
}
]
}
]
And when I ask for a Venda:
[
{
"id": 1,
"cliente": {
"id": 1,
"vendas": [
{
"id": 1,
"cliente": 1
}
]
}
}
]
The problem is, I don't need the "cliente" information on "vendas" in the first case, but in the second one I need the "cliente" information, However I don't want his "vendas", cause I already got it before;
I already trid #JsonIgnore and didn't work for me, what should I do?
PS: I'm working with GSON to get the .Class from JSON, and I get a terrible Exception because sometimes cliente is an Object and sometimes is Integer, so if you guys have another solution that makes cliente and vendas don't change their type, i would to know too. :(
I was able to solve this using Jackson's Mix-in feature. The Mixin feature is a class were you can specify json annotations (on the class, fields and getters/setters) and they apply to the bean/pojo you serialize. Basically, a mixin allows adding annotations at run time and without chaning the bean/pojo source file. You use Jackson's module feature to apply a Mixin at run time.
So I created one mixin that dynamically adds #JsonIgnore annotation to vendas getter method of UserAccount class, and another mixin that adds #JsonIgnore annotation to cliente getter method of Venda class.
Here is the modified UserAccount class:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class UserAccount implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private List<Venda> vendas = new ArrayList<>();
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public List<Venda> getVendas() { return vendas; }
public void setVendas(List<Venda> vendas) { this.vendas = vendas; }
public void addVenda(Venda v) {
this.vendas.add(v);
v.setCliente(this);
}
/**
* a Jackson module that is also a Jackson mixin
* it adds #JsonIgnore annotation to getVendas() method of UserAccount class
*/
public static class FilterVendas extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(UserAccount.class, FilterVendas.class);
}
// implementation of method is irrelevant.
// all we want is the annotation and method's signature
#JsonIgnore
public List<Venda> getVendas() { return null; }
}
Here is the modified Venda class:
public class Venda implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private UserAccount cliente;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public UserAccount getCliente() { return cliente; }
public void setCliente(UserAccount cliente) { this.cliente = cliente; }
/**
* a Jackson module that is also a Jackson mixin
* it adds #JsonIgnore annotation to getCliente() method of Venda class
*/
public static class FilterCliente extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Venda.class, FilterCliente.class);
}
// implementation of method is irrelevant.
// all we want is the annotation and method's signature
#JsonIgnore
public UserAccount getCliente() { return null; }
}
}
and the test method with run time object mapper configuration:
public static void main(String... args) {
Venda v = new Venda();
UserAccount ua = new UserAccount();
v.setId(1L);
ua.setId(1L);
ua.addVenda(v);
try {
ObjectMapper mapper = new ObjectMapper();
System.out.println("UserAccount: (unfiltered)");
System.out.println(mapper.writeValueAsString(ua));
mapper = new ObjectMapper();
// register module at run time to apply filter
mapper.registerModule(new Venda.FilterCliente());
System.out.println("UserAccount: (filtered)");
System.out.println(mapper.writeValueAsString(ua));
mapper = new ObjectMapper();
System.out.println("Venda: (unfiltered)");
System.out.println(mapper.writeValueAsString(v));
mapper = new ObjectMapper();
// register module at run time to apply filter
mapper.registerModule(new UserAccount.FilterVendas());
System.out.println("Venda: (filtered)");
System.out.println(mapper.writeValueAsString(ua));
} catch (Exception e) {
e.printStackTrace();
}
}
output:
UserAccount: (unfiltered)
{"id":1,"vendas":[{"id":1,"cliente":1}]}
UserAccount: (filtered)
{"id":1,"vendas":[{"id":1}]}
Venda: (unfiltered)
{"id":1,"cliente":{"id":1,"vendas":[{"id":1,"cliente":1}]}}
Venda: (filtered)
{"id":1}
Thanks guys, I got the solution by this way:
public class CustomClienteSerializer extends JsonSerializer<UserAccount> {
#Override
public void serialize(UserAccount cliente, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
cliente.setVendas(null);
generator.writeObject(cliente);
}
}
and adding this on my venda class:
#JsonSerialize(using = CustomClienteSerializer.class)
#ManyToOne(fetch = FetchType.EAGER)
private UserAccount cliente;
So... I got the json as I wanted!
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);