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.
Related
I've tried several ways of storing a json file in a database but it ends up creating different columns for each entry.
I want to store it as a "json" type in a single column.
Is it possible?
My json file.
users.json
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere#april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
...
]
It's a spring-boot application and I've the relevant controllers and services.
In my domain package. (Address and Company are Embeddable classes)
User.java
#Data
#AllArgsConstructor #NoArgsConstructor
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String username;
private String email;
private String phone;
private String website;
#Embedded
private Address address;
#Embedded
private Company company;
}
The main file (storing in the database)
TypeReference and ObjectMapper are from Jackson
#SpringBootApplication
public class JsondbApplication {
public static void main(String[] args) {
SpringApplication.run(JsondbApplication.class, args);
}
#Bean
CommandLineRunner runner(UserService service) {
return args -> {
ObjectMapper mapper = new ObjectMapper();
TypeReference<List<User>> reference = new TypeReference<List<User>>() {};
InputStream stream = TypeReference.class.getResourceAsStream("/json/users.json");
try {
List<User> users = mapper.readValue(stream, reference);
service.save(users);
System.out.println("Saved!");
} catch (Exception h) {
System.out.println("Unable to save! " + h.getMessage());
}
};
}
}
in mysql it creates different columns for id, name, username, ...
I want to store it in a single column as a json type using spring boot.
Your code reads json into list of User objects and persists to database. You need to write some custom logic to save it as json. There are multiple ways to do it.
You can do something like
1) Add another variable in User class say private String jsonData
2) In #PrePersist method, write serialization logic
3) Mark other attributes with #JsonInclude() - to include in Jackson
#Transient - to ignore in the persistence in separate column. You might not want to add these annotations to id attribute as each json object will be stored against a specific id in database.
So, new attribute would be something like
#NonNull
#Column(columnDefinition = "JSON") // Only if Database has JSON type else this line can be removed
private String jsonData;
PrePersist:
#PrePersist
public void prePersist() {
try {
this.setJsonData(new ObjectMapper().writeValueAsString(this));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
EDIT:
You might face issue with #Transient attributes being null in the #PrePersist method. In that case you can use a setter method. But you have to call this setter everytime any attribute is changed before the save call.
public void setJsonData(String jsonData) {
// Method parameter jsonData is simply ignored
try {
this.jsonData = new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
log.error(e.getMessage());
}
}
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
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.
I need to send map of custom objects Map<String, Set<Result>> from frontend to backend.
So I think it should be possible to build JSON, send it to Controller via Ajax and receive it in Controller via #RequestBody annotation which should bind json to object. right?
Controller:
#RequestMapping(value = "/downloadReport", method = RequestMethod.POST)
public ResponseEntity<byte[]> getReport(#RequestBody Map<String, Set<Result>> resultMap)
{
Context context = new Context();
context.setVariable("resultMap", resultMap);
return createPDF("pdf-report", context);
}
JSON:
{
"result": [
{
"id": 1,
"item": {
"id": 3850,
"name": "iti"
},
"severity": "low",
"code": "A-M-01",
"row": 1,
"column": 1,
"description": "Miscellaneous warning"
}
]
}
Model:
public class Result {
private Integer id;
private Item item;
private String severity;
private String code;
private Integer row;
private Integer column;
private String description;
//getter & setters
//hashCode & equals
}
public class Item {
private Integer id;
private String name;
//getter & setters
}
After send such a JSON like above by ajax I am getting error message from browser:
The request sent by the client was syntactically incorrect
If I change JSON to send empty set like below then it works but of course my map has empty set:
{"result": []}
So, Why I am not able to receive filled map with set of objects? Why binding/unmarshalling do not work as expected and what I should do to make it works?
Note:
I am using Jackson library and marshalling for other case for #ResponseBody works fine. Problem is with unmarshalling and binding object via #RequestBody.
In order for jackson to properly deserialize your custom classes you need to provide #JsonCreator annotated constructor that follows one of the rules defined in the java doc. So for your Item class it could look like this:
#JsonCreator
public Item(#JsonProperty("id") Integer id,
#JsonProperty("name") String name) {
this.id = id;
this.name = name;
}
you have to deal with map differently,
first create wrapper class
public MyWrapperClass implements Serializable {
private static final long serialVersionUID = 1L;
Map<String, List<String>> fil = new HashMap<String, List<String>>();
// getters and setters
}
then you should take request in controller,
#PostMapping
public Map<String,List<String>> get(#RequestBody Filter filter){
System.out.println(filter);
}
Json Request should be like
{
"fil":{
"key":[
"value1",
"value2"
],
"key":[
"vakue1"
]
}
}
Lets say i have a JSON like:
{
"assignments": [
{
'id': '111',
'activities': [
{
'activity': 'Activity 1',
},
{
'activity': 'Activity 2'
}
]
},
{
'id': '2222',
'Activities': [
{
'activity': 'Activity 1'
}
]
}
]
}
And I'm using GSON to parse it. I have a correctly set up Javabean and can access id without problems. How do i get the activities per id / object?
EDIT: more code:
public class Assignment {
private String id;
public String getId() {
return id;
}
}
Gson mGson= new Gson();
assignmentList=mGson.fromJson(json, AssignmentList.class);
assignmentList.getAssignments().get(0).getId());
I'd create another Bean for Activities since it is a JSON object in itself.
class Assignment {
private String id;
private List<Activity> activities; //getters and setters for this.
public String getId() {
return id;
}
}
class Activity {
private String activity; //Getters and setters
}
Gson mGson= new Gson();
assignmentList=mGson.fromJson(json, AssignmentList.class);
assignmentList.getAssignments().get(0).getActivities.get(1);
If there are only primitive types in the class, just defining two Java classes should be enough, just make sure not to inherit the class from GenericJson, it breaks the recursive operation of a Gson parser.