Lets say we have the following JSON example:
{
"teachers": [{
"id": "abc",
"payment": 10,
"name": "xyz",
"clases": ["1", "3"]
}, {
"id": "qwe",
"payment": 12,
"name": "xcv",
"classes": ["1", "2"]
}],
"classes": [{
"id": "1",
"room": 7
}, {
"id": "2",
"room": 1
}, {
"id": "3",
"room": 2
}]
}
I would like to deserialize it to Java objects (getters/setters ommited):
class Teacher {
private String id;
private double payment;
private String name;
private List<CLassRoom> classRooms;
}
class ClassRoom {
private String id;
private int room;
}
As you see, we have a references here. I know I can deserialize it with Jackson (and would like to) but the problem is that I cannot touch DTO itself (so annotations are not possible, would also like to avoid wrappers (many classes)). Also, it would be nice if the "configuration" of deserialization was in separate file (json schema for example). I would also like to avoid some tags given by user - he should only pass me the values. Moreover, he should know where is the error, if he made some mistake.
Also, it would be nice if I could manipulate name of field in json (some clients may have different habits).
I didn't find anything which satisffied all of above requirements(entity reference and error handling are the most important). However - I just have heard about json schema, so maybe it provides such functionality (but I didn't find it though). Any helpful reference/example/lib? I will appreciate any help.
Just to be correct - imagine that the given json is a RELATIONAL database snapshot of the instance. I just want to create whole entity like the hibernate (or actually JPA) does :)
1. add jar of import org.json.JSONObject.
2. JSONObject object = new JSONObject(list)
2.1 object.has("teachers") if it is exists
2.2 JSONArray teacherArray = (JSONArray) object.get("teachers");
2.3 JSONObject teacherJsonObject = teacherArray .getJSONObject(0);
(if you have more than jsonobject in json arrary then itrate it.)
2.4 if(teacherJsonObject .has("id"))//you can check existence like this.
String id=teacherJsonObject .getString("id");
String payment=teacherJsonObject .getString("payment");
String name=teacherJsonObject .getString("name");
It may not be the best solution, but it's a working one.
Let's create a Parser class like the following:
public class Parser {
private List<Teacher> teachers;
private List<ClassRoom> classes;
public void parse() {
for (Teacher teacher : teachers) {
for (String classRoomId : teacher.getClasses()) {
for (ClassRoom classRoom : classes) {
if (classRoom.getId().equals(classRoomId)) {
teacher.getClassRooms().add(classRoom);
}
}
}
}
}
}
Modify your ClassRoom class to have a getter on the id field:
public class ClassRoom {
private String id;
private int room;
public String getId() {
return id;
}
}
And your Teacher class to get the Ids of classes AND the classRooms references:
public class Teacher {
private String id;
private double payment;
private String name;
private String[] classes;
private List<ClassRoom> classRooms = new ArrayList<>();
public String[] getClasses() {
return classes;
}
public List<ClassRoom> getClassRooms() {
return classRooms;
}
}
If you use the Gson library, you could then just parse your JSON like that:
Gson gson = new Gson();
Parser parser = gson.fromJson(jsonString, Parser.class);
parser.parse;
Now, every teacher will have their classRooms correctly referenced.
Related
I have created customer json file as below:
[
{
"firstName": “test”,
"lastName": “temp”,
"age": 35,
"emailAddress": “test#Gmail.com",
"address": {
"streetAddress": “test testing“,
"city": “city”,
"postCode": “12343546”,
"state": “state”,
"country": “cy”,
"county": “abc”
},
"phoneNumber": {
"home": "012345678",
"mob": "0987654321"
}
},
{
"firstName": “tug”,
"lastName": “kjk”,
"age": 35,
"emailAddress": “jhgj#Gmail.com",
"address": {
"streetAddress": “jh hjgjhg ,
"city": “kjhjh”,
"postCode": "122345",
"state": “jhgl”,
"country": “jaj”,
"county": “jhgkg”
},
"phoneNumber": {
"home": "012345678",
"mob": "0987654321"
}
}
]
For the Customer JSON data file, I have created below JSON datareader class:
public class JsonDataReader {
private final String customerFilePath = new ConfigFileReader().getTestDataResourcePath() + "Customer.json";
private List<Customer> customerList;
public JsonDataReader(){
customerList = getCustomerData();
}
private List<Customer> getCustomerData() {
Gson gson = new Gson();
BufferedReader bufferReader = null;
try {
bufferReader = new BufferedReader(new FileReader(customerFilePath));
Customer[] customers = gson.fromJson(bufferReader, Customer[].class);
return Arrays.asList(customers);
}catch(FileNotFoundException e) {
throw new RuntimeException("Json file not found at path : " + customerFilePath);
}finally {
try { if(bufferReader != null) bufferReader.close();}
catch (IOException ignore) {}
}
}
public final Customer getCustomerByName(String customerName){
for(Customer customer : customerList) {
if(customer.firstName.equalsIgnoreCase(customerName)) return customer;
}
return null;
}
}
Created POJO class as below:
public class Customer {
public String firstName;
public String lastName;
public int age;
public String emailAddress;
public Address address;
public PhoneNumber phoneNumber;
public class Address {
public String streetAddress;
public String city;
public String postCode;
public String state;
public String country;
public String county;
}
public class PhoneNumber {
public String home;
public String mob;
}
}
This is working fine so far as there is only one JSON data file, however I will create more JSON data files, so may be I have to create multiple POJOs for each one, but is there any way I can write common generic jsondatareader class for all those JSON files?
A class (or an Object) is a well defined entity. By well defined I mean that its structure is known at compile time, and cannot be changed after that point.
Having to create multiple classes to represent multiple JSON documents is perfectly fine. So if you're worried about the amount of files you'll create, it's a non-problem.
But, if the JSON document structure will keep changing along with every request, there is no point in defining a series of classes. To handle totally dynamic JSON you should stick with what Gson offers you. That is JsonElement and its subclasses.
JsonElement
> JsonArray
> JsonObject
> JsonPrimitive
> JsonNull
That's all what is needed to describe a JSON object.
If that is the case then why not convert JSON into a Map instead of a POJO! If you go POJO route then you will utilizing Jackson or GSon heavily in your code base adding bunch of utility methods to iterate over every resulting JSonArray or JSonelements.
So, I have an input JSON that looks like this:
[{
"added": "2014-02-01T09:13:00Z",
"author": {
"id": "1",
"name": "George R R Martin",
"added_on": "2013-02-01T09:13:00Z"
},
"book": {
"id": "12",
"name": "Game of Thrones",
"genre": "Fantasy Fiction"
}
},
{
"added": "2015-02-01T09:13:00Z",
"author": {
"id": "2",
"name": "Patrick Rothfuss",
"added_on": "2012-09-13T011:40:00Z"
},
"book": {
"id": "15",
"name": "The Name of the Wind",
"genre": "Fantasy Fiction"
}
}, {
"added": "2016-02-01T09:13:00Z",
"author": {
"id": "2",
"name": "Patrick Rothfuss",
"added_on": "2012-09-13T011:40:00Z"
},
"book": {
"id": "17",
"name": "The Wise Man's Fear",
"genre": "Fantasy Fiction"
}
}]
I need to group it basis on author.id. An author will have one object and a list of all the books he's authored.
This is what I expect the output:
[
{
"author": "George R R Martin",
"added_on": "2013-02-01T09:13:00Z",
"books": [
{
"book_name": "Game of Thrones",
"added": "2014-02-01T09:13:00Z"
}
]
},
{
"author": "Patrick Rothfuss",
"added_on": "2012-09-13T011:40:00Z",
"books": [
{
"book_name": "The Name of the Wind",
"added": "2015-02-01T09:13:00Z"
}, {
"book_name": "The Wise Man's Fear",
"added": "2016-02-01T09:13:00Z"
}
]
}
]
I tried doing it through a normal for loop -- it works. But, just for the sake of learning more about Streams, I want to try it out using Streams.
I tried this:
Map<Author, List<Book>> collect = authorsList.stream()
.collect(Collectors.groupingBy(AuthorBookObj::getAuthor,
Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));
But, didn't get what I needed. Instead, it created three Maps instead of two.
Also tried this:
Map<AuthorTuple, List<Book>> collect = authorsList.stream()
.collect(Collectors.groupingBy(authors -> new AuthorTuple(authors.getAuthor().getId(),
authors.getAuthor().getName(), authors.getAuthor().getAddedOn()),
Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));
It also gives me three objects in the list. I expected to have two authors and corresponding books for each author.
AuthBookObj:
public class AuthorBookObj
{
private String id;
private Author author;
private Book book;
private String added;
//getter, setter
}
public class Article
{
private String name;
private String id;
private String genre;
}
public class Author
{
private String name;
private String added_on;
private String id;
}
The problem is not the way you handle the stream, it is in the equality of the objects.
The correct way is to use this code:
Map<Author, List<Book>> collect = authorsList.stream()
.collect(Collectors.groupingBy(AuthorBookObj::getAuthor,
Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));
But now you are comparing Author objects, since the objects are different you get three entries. You need to add a hashcode and equals in the Author object that will compare the objects on the author id.
//code generated from intellij.
// Author.java
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Author author = (Author) o;
return getId() == author.getId();
}
#Override
public int hashCode() {
return Objects.hash(getId());
}
If you don't have limitation on creating new POJO classes on requirement, i will do in this way
First to parse the input JSON to java object
Response class with AuthorDetails and BookDetails class
class Response {
private String addedOn;
private AuthorDetails author;
private BookDetails book;
}
AuthorDetails
class AuthorDetails {
private String id;
private String name;
private String addedOn;
}
BookDetails
class BookDetails {
private String id;
private String name;
private String gener;
}
And i will map the input json to List<Response>
List<Response> list = Arrays.asList(new Response());
Then now converting List<Response> into desired output i have added couple of POJO classes
AuthorAndBooks
class AuthorAndBooks {
#JsonProperty("author")
private String author;
#JsonProperty("added_on")
private String addedOn;
#JsonProperty("books")
List<AuthorBooks> books;
}
AuthorBooks
class AuthorBooks {
#JsonProperty("book_name")
private String name;
#JsonProperty("added")
private String added;
}
Now do group by based on author name
Map<String, List<Response>> group = list.stream().
collect(Collectors.groupingBy(res->res.getAuthor().getName()));
And now for every Author add the books
List<AuthorAndBooks> authorBooks = group.entrySet().stream().
map(entry->{
AuthorAndBooks ab = new AuthorAndBooks();
ab.setAuthor(entry.getKey());
ab.setAddedOn(entry.getValue().stream().findFirst().get().getAddedOn());
ab.setBooks(entry.getValue().stream().map(authorBook->{
AuthorBooks books = new AuthorBooks();
books.setName(authorBook.getBook().getName());
books.setAdded(authorBook.getAddedOn());
return books;
}).collect(Collectors.toList()));
return ab;
}).collect(Collectors.toList());
First of all want to pay attention to "added" field from input JSON. What does this belong to? I guess it belongs to Book object. If so it would be good to place this field (if it possible) inside Book object. Then you need to deserialize this json to java objects. It can be done by com.fasterxml.jackson.databind.ObjectMapper But you can use any json framework for this.
ObjectMapper mapper = new ObjectMapper();
AuthorBookObj[] objs = mapper.readValue(inputJson, AuthorBookObj[].class);
Then you need to group these objects and your first solution is well suited:
Map<Author, List<Book>> collect = Arrays.stream(objs)
.collect(groupingBy(AuthorBookObj::getAuthor,
mapping(AuthorBookObj::getBook, toList())));
How it was mentioned in previous answer you need to make sure there are equals/hashcode methods in your class that is used for as key in Map (In this case Author). The main confuse now is that desirable json output doesn't represent Map. It is just list of some custom object with fields like author, added_on, books which is list also.
So to achieve this goal you need to transform your Map<Author, List<Book>> to list of custom objects. For example:
public class PublicationInfo {
private String author;
private String added_on;
private List<BookBriefInfo> books;
...
}
public class BookBriefInfo {
private String book_name;
private String added;
...
}
List<PublicationInfo> infos = new ArrayList<>();
for (Map.Entry<Author, List<Book>> entry : collect.entrySet()) {
PublicationInfo info = new PublicationInfo();
info.setAuthor(entry.getKey().getName());
info.setAdded_on(entry.getKey().getAdded_on());
List<BookBriefInfo> bookInfos = new ArrayList<>();
for (Book book : entry.getValue()) {
bookInfos.add(new BookBriefInfo(book.getBook_name(), book.getAdded()))
}
info.setBooks(bookInfos);
}
Finally it can be serialized:
String jsonResult = mapper.writeValueAsString(infos);
By the way, to get json output formatted just configure it:
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
You must override equals and hashCode. If you fail to do so, your class will violate the general contract for hashCode, which will prevent it from functioning properly in collections such as HashMap and HashSet. The Author class’s failure to override hashCode causes the two equal instances to have unequal hash codes, in violation of the hashCode contract. Add this to your Author class.
#Override
public int hashCode() {
return id.hashCode();
}
#Override
public boolean equals(Object obj) {
return obj instanceof Author && ((Author) obj).getId().equals(id);
}
With that in place, the following code snippet should work as expected.
Map<Author, List<Article>> booksByAuthor = authorsList.stream()
.collect(Collectors
.groupingBy(AuthorBookObj::getAuthor,
Collectors.mapping(AuthorBookObj::getBook, Collectors.toList())));
My Spring Boot app makes a call to a REST API and receives a JSON with a varying number of entities. E.g.
{
"content": {
"guest_1": {
"name": {
"firstName": "a",
"lastName": "b"
},
"vip": false
},
"guest_2": {
"name": {
"firstName": "c",
"lastName": "d"
},
"vip": false
},
...more guests omitted...
}
}
There can be 1 to many guests and I don't know their number upfront. As you can see, they aren't in an array, they are objects instead.
I'd like to avoid deserializing into a class like
public class Content {
#JsonProperty("guest_1")
private Guest guest1;
#JsonProperty("guest_2")
private Guest guest2;
// More Guests here each having their own field
}
What I'd like to use is
public class Content {
private List<Guest> guests;
}
The #JsonAnySetter annotation I read about at https://www.baeldung.com/jackson-annotations looks promising but I couldn't get it to work.
3.2. Convert to an object at https://www.baeldung.com/jackson-json-node-tree-model looks also good but it didn't work out either.
I'm not sure if I can make Jackson do this in a declarative way or I should write a custom JsonDeserializer. Could you please help me?
#JsonAnySetter will work as it allows to specify a POJO type as second parameter. You could recreate the example JSON as, omitting setXXX() and getXXX() methods on POJOs for clarity:
private static class Content {
private Guests content;
}
private static class Guests {
private List<Guest> guests = new ArrayList<>();
#JsonAnySetter
private void addGuest(String name, Guest value) {
guests.add(value);
}
}
private static class Guest {
private Name name;
private boolean vip;
}
private static class Name {
private String firstName;
private String lastName;
}
With your JSON example will produce:
Content root = new ObjectMapper().readValue(json, Content.class);
root.getContent().getGuests().stream()
.map(Guest::getName)
.map(Name::getFirstName)
.forEach(System.out::println); // a, c
I'm trying to workout how I should be structuring my JSON objects to best comply with http://jsonapi.org/format/
If I used the following classes:
Page (main object)
public class Page {
#Id
#ObjectId
private String id; //Used for mongodb
#JsonProperty
private String type;
#JsonProperty
private Attribute attributes;
#JsonProperty
private Meta meta;
public Page() {
// Jackson deserialization
}
// Getters and setters
}
Attributes (nested into page)
public class Attribute {
#JsonProperty
private Date created = new Date();
#JsonProperty
private String title;
#JsonProperty
private String body;
public Attribute() {
// Jackson deserialization
}
public Attribute(Date created, String title, String body) {
this.created = created;
this.title = title;
this.body = body;
}
// Getters and setters
}
Meta (nested into page)
public class Meta {
#JsonProperty
private List<String> authors;
public Meta() {
}
public Meta(List<String> authors) {
this.authors = authors;
}
// Getters and setters
}
I can create this object with a post such as:
{
"type": "page",
"attributes": {
"title": "This is the title",
"body": "<p>This is a long section of html, other stuff</p>"
},
"meta": {
"authors": [
"Steve",
"John",
"Sam"
]
}
}
And the resulting JSON object is created:
{
id:"56cbed5036f66b05dc2df841",
type:"page",
attributes:{
created:1456205138886,
title:"This is the title",
body:"<p>This is a long section of html, other stuff</p>"
},
meta:{
authors:[
"Steve",
"John",
"Sam"
]
}
}
The question(s):
Is creating multiple classes the way I have the optimal/correct way of creating nested JSON objects, and should I be trying to wrap this all inside "data:" as per the link above states is a MUST do? If that is the case should I create a single POJO called Data which contains the Page object?
When looking for information around this type of thing all I seem to be able to find is people asking how to deserialize JSON into POJOs, which isn't what I'm looking for.
Really want to find some best practises here for writing APIs.
you should start then from what kind of 'behavior' your objects expose and how you want your API to expose.
Alhough there is no silver bullet, there is a good amount of literature around which can guide you in the right direction of how to model your API (and in turn your objects)
here are a few links:
http://restcookbook.com
http://www.infoq.com/minibooks/domain-driven-design-quickly
Value vs Entity objects (Domain Driven Design)
Personally, and -in general-, I create POJOs, but like also #cricket_007 mentioned it's kind of opinionated.
HTH.
I am making a query call to freebase and I receive a JSON response. The response has the following structure:
{
"code": "/api/status/ok",
"result": [
{
"/common/topic/image": [{
"guid": "#9202a8c04000641f8000000004b67f6d"
}],
"/people/person/profession": [{
"name": "Critic"
}],
"id": "/en/michael_jackson_1942",
"name": "Michael Jackson",
"type": "/people/person"
},
{
"/common/topic/image": [{
"guid": "#9202a8c04000641f800000001b90fdea"
}],
"/people/person/profession": [{
"name": "Actor"
}],
"id": "/en/michael_jackson_1970",
"name": "Michael Jackson",
"type": "/people/person"
}
],
"status": "200 OK",
"transaction_id": "cache;cache03.p01.sjc1:8101;2012-01-16T18:28:36Z;0055"
}
I need to parse this response in a ArrayList of java objects using GSON. To do this I need to create the class of the object with get/set and make it available to parse. Or is there another simpler way to do things ? I have used simple JSON strings by now, but in this case I can't remake the structure of the class I need. Basically in the end I need something like ArrayList<Person> where Person has all the attributes from the json string.
Any help is appreciated. Thank you.
The final solution, according with the answer below
public class FreebaseResponse {
#SerializedName("code")
public String code;
#SerializedName("result")
public ArrayList<Person> result;
#SerializedName("status")
public String status;
#SerializedName("transaction_id")
public String transaction_id;
}
public class Person {
#SerializedName("/common/topic/image")
public ArrayList<Person.Guid> imageGuid;
#SerializedName("/people/person/profession")
public ArrayList<Person.Profession> profession;
#SerializedName("id")
public String id;
#SerializedName("name")
public String name;
#SerializedName("type")
public String type;
private class Guid
{
#SerializedName("guid")
public String guid;
}
private class Profession
{
#SerializedName("name")
public String name;
}
}
I guess you can create a FreebaseResponse class that contains code, result (ArrayList<Person>), etc fields and use Gson to deserialize. the names that are not valid identifiers, e.g. /common/topic/image will be a problem. I haven't tried it myself but it seems to me that SerializedName annotation should do the trick.
If you need all the fields, the way you mentioned seems to me like the way to go. If you only want a small part of the data, you can get it directly. In general, I think that since JSON is meant for object representation, it is better to create the appropriate class. It will ease your way in the future as well, as you will need more from this data.