I have 2 entities
Product:
#Id #GeneratedValue
private Long id;
private String name;
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private Product parentProduct;
#OneToMany(fetch = FetchType.LAZY)
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<Product> childProduct;
#OneToMany(mappedBy="product", fetch = FetchType.LAZY)
#JsonManagedReference #JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<Image> images;
Image:
#Id
#GeneratedValue
private Long id;
private String type;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JsonBackReference
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private Product product;
the lazy relationship are being loaded when I call from a RestController but when I call from my main method (Spring Boot) they came empty.
What I do to maintain lazy when Json serialize.
json return:
[ {
"id" : 1,
"name" : "Passatempo Pacote",
"description" : "Cookies Package",
"images" : [ {
"id" : 2,
"type" : "png"
}, {
"id" : 1,
"type" : "jpeg"
} ]
}, {
"id" : 2,
"name" : "Passatempo",
"description" : "Cookies",
"parentProduct" : {
"id" : 1,
"name" : "Passatempo Pacote",
"description" : "Cookies Package",
"images" : [ {
"id" : 2,
"type" : "png"
}, {
"id" : 1,
"type" : "jpeg"
} ]
}
} ]
Images must be empty because lazy config on property
The Json Serializer will call the get method, which will load your lazy field.
If you don't want these lazy field in json, you could annotate them #JsonIgnore.
#JsonInclude(JsonInclude.Include.NON_EMPTY) means the field will be ignored only if it's empty.
Use fetch = FetchType.EAGER instead of fetch = FetchType.LAZY.
Related
I'm building a REST API with Spring Boot to retrieve boat information. I'm using Spring Data Rest and Spring Data JPA. When I get the data from the API, I don't know why the relationship data are not with the others informations.
Do I have to configure something in Spring to get the relationship with my data ?
Here is my file.
Boat entity:
#Entity
#Table(name="boat")
#Data
public class Boat {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "type_id", nullable = false)
#JsonBackReference
private BoatType type;
}
Boat type entity :
#Entity
#Table(name = "boat_type")
#Data
public class BoatType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "type")
#JsonManagedReference
private Set<Boat> boats;
}
Boat repository :
#CrossOrigin("http://localhost:4200")
public interface BoatRepository extends JpaRepository<Boat, Long> {
}
JSON response :
{
"_embedded": {
"boats": [
{
"id": 1,
"name": "Boat 1",
"description": "A brief description of the boat 1",
"_links": {
"self": {
"href": "http://localhost:8080/api/boats/1"
},
"boat": {
"href": "http://localhost:8080/api/boats/1"
},
"type": {
"href": "http://localhost:8080/api/boats/1/type"
}
}
},
...
]
}
Result expected (with the type object too) :
{
"_embedded": {
"boats": [
{
"id": 1,
"name": "Boat 1",
"description": "A brief description of the boat 1",
"type": {
"id": 1,
"name": "Motorboats"
},
"_links": {
"self": {
"href": "http://localhost:8080/api/boats/1"
},
"boat": {
"href": "http://localhost:8080/api/boats/1"
},
"type": {
"href": "http://localhost:8080/api/boats/1/type"
}
}
},
...
]
}
I think that the problem is related with Spring Data Rest because when i do the same app with my own controller and repository, i get the data I need.
Is there a way to "configure" spring data rest?
It seems like you've used #JsonBackReference and #JsonManagedReference the other way around, than you needed. You've put #JsonBackReference on the type field in your Boat class, whereas its documentation states:
[...] Linkage is handled such that the property annotated with this annotation is not serialized
So it seems like you need to put #JsonManagedReference annotation on it instead (see: JsonManagedReference documentation) and put #JsonBackReference on boats in your BoatType class.
Alternatively, you could consider using #JsonIdentityInfo instead. See: the documentation.
Also, this article might be helpful. It explains various ways to handle bidirectional relationships using Jackson.
Change #JsonManagedReference and #JsonBackReference to #JsonIgnoreProperties.
In your case:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "type")
#JsonIgnoreProperties(value = {"type"})
private Set<Boat> boats;
and
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "type_id", nullable = false)
#JsonIgnoreProperties(value = {"boats"})
private BoatType type;
You will avoid the infinity loop in json result and get all reference objects (relationships).
The Boat response includes a uri to your BoatType resource by default since you defined a rest repository for your BoatType resource (docs)
To override this behaviour, define a projection to expose the boat type data (docs):
#Projection(name = "boatDetail", types = { Boat.class })
interface BoatDetail {
// ... all other fields you want included in the response
BoatType getType();
}
Then include the projection as a query parameter:
{apiUrl}boats/1?projection=boatDetail
The response should now include the boat type data:
{
"id": 1,
"name": "Boat 1",
"description": "A brief description of the boat 1",
"type": {
"id": 1,
"name": "Motorboats"
},
"_links": {
// ...
}
}
To automatically include the projection on a collection of resources, use an excerpt (docs):
#RepositoryRestResource(excerptProjection = BoatDetail.class)
interface BoatRepository extends JpaRepository<Boat, Long> {}
Then the http response:
{
"_embedded":{
"boats":[
{
"id":1,
"name":"Boat 1",
"description":"A brief description of the boat 1",
"type":{
"id":1,
"name":"Motorboats"
},
"_links":{
//...
}
},
// ...
]
}
}
While saving json object containing multiple jsons, the json object is being saved as a single array instead of multiple rows.
sample json:
[
{
"id" : 1, -- this is not a primary key and not unique but cannot be null
"name" : "John Doe",
"phone" : [
{ "type" : "home", "ref" : "111-111-1234"},
{ "type" : "work", "ref" : "222-222-2222"}
]
},
{
"id" : 2, -- this is not a primary key and not unique but cannot be null
"name" : "Jane Doe",
"phone" : [
{ "type" : "home", "ref" : "111-111-1234"},
{ "type" : "work", "ref" : "222-222-2222"}
]
}
]
This is what i need after saving in the database
id name phone
1 John Doe { "type" : "home", "ref" : "111-111-1234"}
1 John Doe { "type" : "work", "ref" : "222-222-2222"}
2 Jane Doe { "type" : "home", "ref" : "111-111-1234"}
2 Jane Doe { "type" : "work", "ref" : "222-222-2222"}
This is what I am getting
id name phone
1 John Doe [{ "type" : "home", "ref" : "111-111-1234"},{ "type" : "work", "ref" : "222-222-2222"}]
2 Jane Doe [{ "type" : "home", "ref" : "111-111-1234"},{ "type" : "work", "ref" : "222-222-2222"}]
here is how i am parsing the json object to pojo and saving to db
#Entity
#Table(name="person")
public class person{
private Integer id;
private String name;
private String phone;
#Transient
JsonNode phoneJson;
private static OhjectMapper mapper = new ObjectMapper();
getter/setter
#Transient
public JsonNode getPhoneJson(){
return phoneJson;
}
public void setPhoneJson(JsonNode phoneJson){
this.phoneJson = phoneJson;
}
#JsonIgnore
#Column(name="phone")
public String getPhone() throws Exception{
return mapper.writeValueAsString(phoneJson);
}
public void setPhone(String phone) throws Exception{
this.phone = mapper.readTree(phone);
}
}
dao- save
personRepository.save(person)
any help would be appreciated.
UPDATE
Multiple jSON Column
[
{
"id" : 1, -- this primary key and not unique but cannot be null
"name" : { --this element can be empty/null
"first" : "John",
"last" : "Doe"
},
"phone" : [
{ "type" : "home", "ref" : 1111111234},
{ "type" : "work", "ref" : 2222222222}
]
},
{
"id" : 2, -- this primary key and not unique but cannot be null
"name" : {
"first" : "Jane",
"last" : "Doe"
},
"phone" : [
{ "type" : "home", "ref" : 1111111234},
{ "type" : "work", "ref" : 2222222222}
]
}
]
how do i get result as below
id name phone
1 [{John},{Doe}] { "type" : "home", "ref" : "111-111-1234"}
1 [{John},{Doe}] { "type" : "work", "ref" : "222-222-2222"}
2 [{Jane},{Doe}] { "type" : "home", "ref" : "111-111-1234"}
2 [{Jane},{Doe}] { "type" : "work", "ref" : "222-222-2222"}
You need to duplicate Person objects n times where n is size of phone array. To make it clear, I propose, to create two separate models which we can use separately for parsing JSON and saving in DB. Below you can find simple example which:
Parses JSON to List<JsonPerson>
Converts List<JsonPerson> to List<Person>
Prints List<Person> (you can save it to DB)
Example:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
CollectionType personsType = mapper.getTypeFactory().constructCollectionType(List.class, JsonPerson.class);
// parse
List<JsonPerson> jsonPersons = mapper.readValue(jsonFile, personsType);
// convert
List<Person> persons = jsonPersons.stream()
.map(p -> p.mapTo(mapper))
.flatMap(List::stream)
.collect(Collectors.toList());
persons.forEach(System.out::println);
// save persons to DB
// ...
}
}
class JsonPerson {
private Integer id;
private String name;
private ArrayNode phone;
public List<Person> mapTo(ObjectMapper mapper) {
List<Person> persons = new ArrayList<>();
phone.elements().forEachRemaining(phone -> {
persons.add(map(mapper, phone));
});
return persons;
}
private Person map(ObjectMapper mapper, JsonNode p) {
Person person = new Person();
person.setId(id);
person.setName(name);
try {
person.setPhone(mapper.writeValueAsString(p));
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
return person;
}
// getters, setters, toString
}
class Person {
private Integer id;
private String name;
private String phone;
// getters, setters, toString
}
Above code prints:
Person{id=1, name='John Doe', phone='{"type":"home","ref":"111-111-1234"}'}
Person{id=1, name='John Doe', phone='{"type":"work","ref":"222-222-2222"}'}
Person{id=2, name='Jane Doe', phone='{"type":"home","ref":"111-111-1234"}'}
Person{id=2, name='Jane Doe', phone='{"type":"work","ref":"222-222-2222"}'}
Above code separates parsing JSON from other parts. Also, do not create ObjectMapper in each POJO. POJO should not know anything about ObjectMapper and Jackson.
Update
Because name is JSON Object you can create new POJO - Name with first and last properties or treat it similarly to phone and deserialise to JsonNode:
class JsonPerson {
private Integer id;
private JsonNode name;
private ArrayNode phone;
public List<Person> mapTo(ObjectMapper mapper) {
List<Person> persons = new ArrayList<>();
phone.elements().forEachRemaining(phone -> {
persons.add(map(mapper, phone));
});
return persons;
}
private Person map(ObjectMapper mapper, JsonNode p) {
Person person = new Person();
person.setId(id);
person.setName(getNameAsString());
try {
person.setPhone(mapper.writeValueAsString(p));
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
return person;
}
private String getNameAsString() {
if (name == null) {
return null;
}
StringBuilder builder = new StringBuilder();
if (name.isObject()) {
ObjectNode nameObject = (ObjectNode) name;
builder.append("[");
builder.append("{").append(nameObject.get("first")).append("}");
builder.append(",");
builder.append("{").append(nameObject.get("last")).append("}");
builder.append("]");
}
return builder.toString();
}
// getters, setters, toString
}
After change above code should print:
Person{id=1, name='[{"John"},{"Doe"}]', phone='{"type":"home","ref":1111111234}'}
Person{id=1, name='[{"John"},{"Doe"}]', phone='{"type":"work","ref":2222222222}'}
Person{id=2, name='[{"Jane"},{"Doe"}]', phone='{"type":"home","ref":1111111234}'}
Person{id=2, name='[{"Jane"},{"Doe"}]', phone='{"type":"work","ref":2222222222}'}
getNameAsString method is simplified, you need to handle all corner cases and create String representation better for null, empty and semi-empty nodes.
I'm writing a Java spring boot mvc application with possibility of data export / import. I wrote a wrapper class, that shoud serialize / deserialize data for Student class. It works good for export, but during import an error appears
com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name
'student' does not match expected ('students') for type [simple type,
class org.bajiepka.courseApp.wrappers.Response] at [Source:
(FileInputStream); line: 2, column: 3]
here is my maven jackson dependency:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
I use wrapper class Response for list of students,
package org.bajiepka.courseApp.wrappers;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import org.bajiepka.courseApp.domain.Student;
#JsonRootName("import")
public class Response {
private Iterable<Student> students;
#JsonProperty("students")
public Iterable<Student> getStudents(){
return students;
}
public void setStudents(Iterable<Student> students) {
this.students = students;
}
}
And this is my Student class:
#Data
#Entity
public class Student {
private #Id #GeneratedValue Long id;
private #Version int version;
#NotNull
private String name;
private String address;
private String phone;
private Integer gradeBook;
private float averageProgress;
public Student() {}
}
}
This is the method that created an export file:
public String write(boolean toFile){
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
try {
String result = writer.writeValueAsString(response);
if (toFile){
result = fileService.writeToFile(result);
}
return result;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return "";
}
after export i get the file with json, that i can't convert to java objects:
{
"student" : [ {
"id" : 1,
"version" : 0,
"name" : "Rachel Jessica Parker",
"address" : "Pentaho",
"phone" : "111-22-33",
"gradeBook" : 1000121,
"averageProgress" : 0.0
}, {
"id" : 2,
"version" : 0,
"name" : "Bobby Jackson Junior",
"address" : "Illinois",
"phone" : "222-33-44",
"gradeBook" : 1000122,
"averageProgress" : 0.0
}, {
"id" : 3,
"version" : 0,
"name" : "Sammy Smith Carlson",
"address" : "Pennsylvania",
"phone" : "333-44-55",
"gradeBook" : 1000123,
"averageProgress" : 0.0
}, {
"id" : 4,
"version" : 0,
"name" : "Harry Dale Harrison",
"address" : "Detroit",
"phone" : "444-55-66",
"gradeBook" : 1000124,
"averageProgress" : 0.0
}, {
"id" : 5,
"version" : 0,
"name" : "Lindsey jefferson Conly",
"address" : "Washington",
"phone" : "555-66-77",
"gradeBook" : 1000125,
"averageProgress" : 0.0
}, {
"id" : 6,
"version" : 0,
"name" : "Mo Williams Jr.",
"address" : "New York",
"phone" : "666-77-88",
"gradeBook" : 1000126,
"averageProgress" : 0.0
} ]
}
And finally this is the method for conversion:
#GetMapping(value = "/import")
public String importFile(#RequestParam Long id){
File importFile = new File(exchangeFileService.findById(id).getName());
if (importFile.exists()) {
try (FileInputStream stream = new FileInputStream(importFile)) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
TypeReference<Response> typeReference = new TypeReference<>(){};
Response response = mapper.readValue(stream, typeReference);
} catch (IOException e) {
e.printStackTrace();
}
}
return "redirect:/exchange/upload";
}
i get mentioned above MismatchInputException at mapper.readValue(stream, typeReference)
ObjectMapper should return me a Response with a list of students but it doesn't...
#UPDATE
I've managed to find the cause of the error.
During the Object -> Json serialization jackson root name is missed...
I've added manually
{
**"import" : {**
"student" : [ {
"id" : 1,
"version" : 0,
"name" : "Rachel Jessica Parker",
"address" : "Pentaho",
"phone" : "111-22-33",
"gradeBook" : 1000121,
"averageProgress" : 0.0
}, ... ]
,
"course" : [ {
"id" : 7,
"version" : 0,
"name" : "Physics for 9-th grade",
"number" : 100292910,
"cost" : 25000.0,
"modules" : 0,
"max_COURSES_PER_STUDENT" : 3,
"modules_PER_COURSE" : 10
}, ... ]
**}**
}
Also i've managed to extend Response class, as it was required... Now i'm trying to find the reason in ObjectMapper during serialization...
The problem was caused due to Java object -> Json serialization without root level.
To add it to Json i've just added it at mapper by withRootName() method and everything works well from now on!
mapper.writer().withDefaultPrettyPrinter().withRootName("import")
I'm using RatPack 1.1.1 with Jackson and it renders the same object "Product" differently. The first "Product" within my main object is returned with the attribute "productCode" but for the others for the same product, it returns only an attribute value "product" with the value of the "productCode"!
Why? is it normal?
Response from the rendering:
ProductCharacteristics=[ {
"id" : 1,
"product" : {
"productCode" : "cold-brew"
},
"attributeCode" : "family",
"attributeValue" : "Kits",
}, {
"id" : 2,
"product" : "cold-brew",
"attributeCode" : "Couleur",
"attributeValue" : "Noir",
}, {
"id" : 3,
"product" : "cold-brew",
"attributeCode" : "Matériaux",
"attributeValue" : "Verre-Plastique",
}
]
Definition of my entity ProductCharacteristic:
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#ToString
class ProductCharacteristic {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#JoinColumn(name = "product_code")
#ManyToOne(optional = false, fetch = FetchType.EAGER, cascade = CascadeType.MERGE)
Product product;
}
Definition of my entity Product:
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="productCode")
#ToString
public class Product {
#Id
String productCode;
}
Why the response is not like that:
ProductCharacteristics=[ {
"id" : 1,
"product" : {
"productCode" : "cold-brew"
},
"attributeCode" : "family",
"attributeValue" : "Kits",
}, {
"id" : 2,
"product" : {
"productCode" : "cold-brew"
},
"attributeCode" : "Couleur",
"attributeValue" : "Noir",
}, {
"id" : 3,
"product" : {
"productCode" : "cold-brew"
},
"attributeCode" : "Matériaux",
"attributeValue" : "Verre-Plastique",
}
]
Thanks for your help!
Course.java
#Entity
#Table
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Course {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#NotEmpty
private String description;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "subject_course", joinColumns = #JoinColumn(name = "subject_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "course_id", referencedColumnName = "id"))
private Set<Subject> subjects = new HashSet<Subject>();
---- getter/setter ----
Subject.java
#Entity
#Table
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Subject {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String description;
#ManyToMany(mappedBy = "subjects", fetch=FetchType.EAGER)
#Cascade({CascadeType.DELETE, CascadeType.SAVE_UPDATE})
private Set<Course> courses = new HashSet<Course>();
---- getter/setter ----
Request configuration in Spring:
#RequestMapping(value = "/courses", method = RequestMethod.GET)
public #ResponseBody ResponseEntity<?> getAllCourses() {
List<Course> courses = courseService.getAllCourses();
if (courses.isEmpty()) {
return new ResponseEntity<Message>(new Message("error", "No course found!"), HttpStatus.NOT_FOUND);
}
return new ResponseEntity<List<Course>>(courses, HttpStatus.OK);
}
Hibernate Version: 4.2.0.Final
Spring Version: 3.2.8.RELEASE
Jackson:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.4</version>
</dependency>
Expecting O/P
[{
"id": 1,
"description": "BCA",
"subjects":[{
"id":1,
"description":"Physics",
"courses":[1,2,3] //Either show blank array or only ids
}]
},{
"id": 2,
"description": "BSC",
"subjects":[{
"id":1,
"description":"Physics",
"courses":[1,2,3]
}]
},{
"id": 3,
"description": "BA",
"subjects":[{
"id":1,
"description":"Physics",
"courses":[1,2,3]
}]
},]
But getting O/P:
[
{
"id": 1,
"description": "BCA",
"subjects": [
{
"id": 1,
"description": "Math",
"staffs": [],
"courses": [
{
"id": 4,
"description": "BDA",
"subjects": [
1
],
"students": []
},
{
"id": 3,
"description": "BBA",
"subjects": [
1
],
"students": []
},
1
],
"students": []
}
],
"students": [
{
"id": 1,
"name": "",
"age": 0,
"gender": null,
"course": 1,
"subjects": []
}
]
},
3,
4
]
As per actual o/p, it is stopping the recursion at second level. But my requirement is not to repeat the same objects data from child... It means Course must not repeat its data in Subject's course property. Similarly, if call the same from Subject then subject should not repeat Course subject property value. It is better to skip, if can't then just display id values separated by comma.
Please advise how to fix this issue.
You can use #JsonIgnore in the Subject class like this:
#ManyToMany(mappedBy = "subjects", fetch=FetchType.EAGER)
#Cascade({CascadeType.DELETE, CascadeType.SAVE_UPDATE})
#JsonIgnore
private Set<Course> courses = new HashSet<Course>();