When I use #JsonUnwrapped on nested field:
public class Person{
private int id;
#JsonUnwrapped
private Father father
//getters/setters
#Data
#AllArgsConstructor
private static class Father {
private String name;
private String surname;
}
And at the same time I use the #JsonCreator:
#JsonCreator // DESERIALIZATION: JSON -> POJO
public Person(...
#JsonProperty("name") String name,
#JsonProperty("surname") String surname) {
(...)
this.father = new Father(name, surname);
with Father being nested class.
I get the error:
Father` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creat
But when I remove the #JsonUnwrapped the field gets deserialised ok but is not flatten during serialisation.
How to assure that Father field will be serialised and deserialised flatten at the same time?
EDIT:
I paste full code:
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
private int id;
private String firstName;
private String lastName;
private boolean active;
private Address address;
private String[] languages;
#JsonIgnore private boolean isTheKing;
#JsonUnwrapped // SERIALIZATIONL POJO -> JSON
private Father father;
#JsonCreator // DESERIALIZATION: JSON -> POJO
public Student(
#JsonProperty("id") int id,
#JsonProperty("firstName") String firstName,
#JsonProperty("lastName") String lastName,
#JsonProperty("active") boolean active,
#JsonProperty("address") Address address,
#JsonProperty("languages") String[] languages,
#JsonProperty("isTheKing") boolean isTheKing,
#JsonProperty("name") String name,
#JsonProperty("surname") String surname) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.active = active;
this.address = address;
this.languages = languages;
this.isTheKing = isTheKing;
this.father = new Father(name, surname);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String[] getLanguages() {
return languages;
}
public void setLanguages(String[] languages) {
this.languages = languages;
}
public Father getFather() {
return father;
}
public void setFather(Father father) {
this.father = father;
}
#Data
#AllArgsConstructor
static class Father {
private String name;
private String surname;
}
}
the following main method fails:
ObjectMapper mapper = new ObjectMapper();
Person myStudent =
mapper.readValue(new File("src/main/resources/data/rest/studentIN.json"), Person.class);
System.out.println(myStudent);
with error:
Exception in thread "main"
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of com.example.demo.Person$Father
(no Creators, like default construct, exist): cannot deserialize from
Object value (no delegate- or property-based Creator)
I use lombok:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
It should work for simple POJO model. Father class should be public:
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
Person.Father father = new Person.Father();
father.setName("Wit");
father.setSurname("Pil");
Person person = new Person();
person.setId(1909);
person.setFather(father);
String json = mapper.writeValueAsString(person);
System.out.println(json);
System.out.println(mapper.readValue(json, Person.class));
}
}
class Person {
private int id;
#JsonUnwrapped
private Father father;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Father getFather() {
return father;
}
public void setFather(Father father) {
this.father = father;
}
#Override
public String toString() {
return "Person{" +
"id=" + id +
", father=" + father +
'}';
}
static class Father {
private String name;
private String surname;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
#Override
public String toString() {
return "Father{" +
"name='" + name + '\'' +
", surname='" + surname + '\'' +
'}';
}
}
}
Above code prints:
{
"id" : 1909,
"name" : "Wit",
"surname" : "Pil"
}
Person{id=1909, father=Father{name='Wit', surname='Pil'}}
I've tested it for many version since 2.6.7 and it works fine.
Related
I have a JSON string that needs to be converted to JAVA Object. I need to wrap some fields into a different JAVA class. The problem I am facing I am not able to wrap it and I get the Java fields as null.
Please see below JSON
{
"first_name": "John",
"last_name": "DCosta",
"age": "29",
"phone": "+173341238",
"address_line_1": "43 Park Street",
"address_line_2": "Behind C21 Mall",
"city": "Cario",
"country": "UK",
"child1": {
"name": "Peter",
"age": "5"
},
"child2": {
"name": "Paddy",
"age": "2"
},
"child3": {
"name": "Premus",
"age": "1"
}
}
Please see my JAVA Classes Below -
Details.java
public class Details {
private Person person;
private Address address;
private Child[] children;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Child[] getChildren() {
return children;
}
public void setChildren(Child[] children) {
this.children = children;
}
}
Person.java
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
#JsonProperty("first_name")
private String firstName;
#JsonProperty("last_name")
private String lastName;
#JsonProperty("age")
private Integer age;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Address.java
import com.fasterxml.jackson.annotation.JsonProperty;
public class Address {
#JsonProperty("phone")
private String phone;
#JsonProperty("address_line_1")
private String addressLine1;
#JsonProperty("address_line_2")
private String addressLine2;
#JsonProperty("city")
private String city;
#JsonProperty("country")
private String country;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
public String getAddressLine2() {
return addressLine2;
}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
Child.java
public class Child {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
My Code to convert JSON to JAVA Object -
String filePath = "test.json";
File file = new File(filePath);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Details details = mapper.readValue(file, Details.class);
System.out.println(details.getPerson());
The problem I am facing is I am getting all the values in the details object are null. If I remove the mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); then I get the below exception
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "first_name" (class learn.springboot.model.Details), not marked as ignorable (3 known properties: "address", "person", "children"])
at [Source: (File); line: 2, column: 17] (through reference chain: learn.springboot.model.Details["first_name"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.ja
I don't think that it is possible to have some wrapper classes and expect Jackson to flatten and extract all of those fields from wrapper classes and map the flat JSON fields to them.
Based on the Details class, Jackson expects having a JSON object like the below one (inner fields are omitted):
{
"person": {},
"address": {},
"children": []
}
So, you have to change the Details class to something like below:
public class Details {
#JsonProperty("first_name")
private String firstName;
#JsonProperty("last_name")
private String lastName;
#JsonProperty("age")
private Integer age;
...
}
I have Entity with 3 fields: id, lastname and phoneNumber. I want to create method which works for update all fields or only one or two.
I use Hibernate and JPA Repository.
When I try to update all fields everything works well but when for example i want to update only lastname without changing of phoneNumber I have in output null insted of old phoneNumber.
Here is my method from Controller:
#PutMapping("/students/update/{id}")
public String updateStudentById(#ModelAttribute Student student, #ModelAttribute StudentDetails studentDetails,
String lastname, String phoneNumber,
#PathVariable Long id) {
Optional<Student> resultOptional = studentRepository.findById(id);
//Student result =resultOptional.get();
resultOptional.ifPresent((Student result) -> {
result.getStudentDetails().setPhoneNumber(studentDetails.getPhoneNumber()); result.getStudentDetails().setLastname(studentDetails.getLastname());
studentRepository.save(result);
});
return "Student updated";
}
The class for update:
#DynamicUpdate
#Entity
public class StudentDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="lastname")
private String lastname;
#Column(name="phone_number")
private String phoneNumber;
public StudentDetails() {
}
public StudentDetails(Long id, String lastname, String phoneNumber) {
this.id = id;
this.lastname = lastname;
this.phoneNumber = phoneNumber;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
The class which has relation with StudentDetails:
#Entity
#Table(name = "student")
#DynamicUpdate
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
//#OneToMany(mappedBy = "student")
#ManyToMany
#JoinTable(name="course_student",joinColumns = #JoinColumn(name="student_id"),
inverseJoinColumns = #JoinColumn(name="course_id"))
private List<Courses> courses;
#OneToOne(cascade = CascadeType.ALL)
// #JoinColumn(name="studen/_details_id") // with this we have dobule student_details column
private StudentDetails studentDetails;
public List<Courses> getCourses() {
return courses;
}
public void setCourses(List<Courses> courses) {
this.courses = courses;
}
public StudentDetails getStudentDetails() {
return studentDetails;
}
public void setStudentDetails(StudentDetails studentDetails) {
this.studentDetails = studentDetails;
}
// Methods for StudentViewController
public String getLastname(){
return studentDetails.getLastname();
}
public String getPhoneNumber(){
return studentDetails.getPhoneNumber();
}
public Student() {
}
public Student(String name, String email, StudentDetails studentDetails) {
// this.id = id;
this.name = name;
this.email = email;
this.studentDetails = studentDetails;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
I was looking for solution and I added #DynamicUpdate but still it doesn't work.
Your code works properly. When you only provide lastName parameter in your request, then the phoneNumber parameter will be mapped to null so you override the phoneNumer property in your entity with this null value.
Change the code in the following way:
resultOptional.ifPresent((Student result) -> {
if(studentDetails.getPhoneNumber()!=null) {
result.getStudentDetails().setPhoneNumber(studentDetails.getPhoneNumber());
}
if(studentDetails.getLastname()!=null) {
result.getStudentDetails().setLastname(studentDetails.getLastname());
}
studentRepository.save(result);
});
Unfortunately it raises an other problem: How will you delete these fields? (How can you set them explicitly to null? )
A possible solution if you check for the "" (empty string) and set the property to null if the parameter is empty string.
It will be a quite messy code anyway...
You should consider using the Spring Data Rest package. It automatically creates all of the standard REST endpoints for your entities and handles all of these PUT/PATCH/POST/DELETE issues out of the box.
why don't you just set the params of your request in you setters?
resultOptional.ifPresent((Student result) -> {
result.getStudentDetails().setPhoneNumber(phoneNumber);
result.getStudentDetails().setLastname(lastname);
studentRepository.save(result);
});
You forget set #OneToOne mapping in StudentDetails - StudentDetails also need field of type Student which will be annotated #OneToOne.
Also you have to ensure, that all of entity fields will be filled - read more about fetch types.
I have a doubt!
I would like to use a class with JavaBean to DTO converter methods and vice versa.
Is my web application, should the methods be synced or should I create an instance of this class when I want to use them?
This is the javabean:
public class Person {
private String name;
private String surname;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname (String surname) {
this.surname = surname;
}
}
This is the dto:
public class PersonDTO {
private String fullName;
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
}
This is the converter class:
public class PersonConverter {
public static PersonDTO fromBeanToDTO (Person Person) {
PersonDTO personDTO = new PersonaDTO();
personDTO.setFullName(person.getNome() + " " + person.getSurname());
return personDTO;
}
}
I have to map this JSONObject into a Java object.
This is my Json:
{"WALLET":{
"ID":"1234",
"BAL":"20.000",
"NAME":"Filomena",
"EMAIL":"filo#gmail.com",
"DOCS":[
{
"ID":"001",
"S":"0",
"TYPE":"CardId",
"VD":"2019"
}
],
"IBANS":[
{
"ID":"001",
"S":"1",
"DATA":"iban",
"SWIFT":"swiftCode",
"HOLDER":"holder"
}
],
"STATUS":"string",
"BLOCKED":"1",
"SDDMANDATES":[
{
"ID":"sddMandateId",
"S":"status",
"DATA":"iban",
"SWIFT":"swiftCode"
}
],
"LWID":"string",
"CARDS":[
{
"ID":"string",
"EXTRA":{
"IS3DS":"string",
"CTRY":"string",
"AUTH":"string",
"NUM":"string",
"EXP":"string",
"TYP":"string"
}
}
],
"FirstName":"string",
"LastName":"string",
"CompanyName":"string",
"CompanyDescription":"string",
"CompanyWebsite":"string"
}
}
This is my Java class:
public class Wallet {
private String id;
private String bal;
private String name;
private String email;
private List<Doc> docs;
private List<Iban> ibans;
private String status;
private String blocked;
private List<SddMandate> sddMandates ;
private String lwid;
private List<Card> cards;
private String firstName;
private String lastname;
private String companyName;
private String companyDescription;
private String companyWebSite;
public Wallet(){
}
public Wallet(String id, String bal, String name, String email, List<Doc> docs, List<Iban> ibans, String status,
String blocked, List<SddMandate> sddMandates, String lwid, List<Card> cards, String firstName,
String lastname, String companyName, String companyDescription, String companyWebSite) {
super();
this.id = id;
this.bal = bal;
this.name = name;
this.email = email;
this.docs = docs;
this.ibans = ibans;
this.status = status;
this.blocked = blocked;
this.sddMandates = sddMandates;
this.lwid = lwid;
this.cards = cards;
this.firstName = firstName;
this.lastname = lastname;
this.companyName = companyName;
this.companyDescription = companyDescription;
this.companyWebSite = companyWebSite;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBal() {
return bal;
}
public void setBal(String bal) {
this.bal = bal;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<Doc> getDocs() {
return docs;
}
public void setDocs(List<Doc> docs) {
this.docs = docs;
}
public List<Iban> getIbans() {
return ibans;
}
public void setIbans(List<Iban> ibans) {
this.ibans = ibans;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getBlocked() {
return blocked;
}
public void setBlocked(String blocked) {
this.blocked = blocked;
}
public List<SddMandate> getSddMandates() {
return sddMandates;
}
public void setSddMandates(List<SddMandate> sddMandates) {
this.sddMandates = sddMandates;
}
public String getLwid() {
return lwid;
}
public void setLwid(String lwid) {
this.lwid = lwid;
}
public List<Card> getCards() {
return cards;
}
public void setCards(List<Card> cards) {
this.cards = cards;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getCompanyDescription() {
return companyDescription;
}
public void setCompanyDescription(String companyDescription) {
this.companyDescription = companyDescription;
}
public String getCompanyWebSite() {
return companyWebSite;
}
public void setCompanyWebSite(String companyWebSite) {
this.companyWebSite = companyWebSite;
}
Now i'm trying to map the object with gson library.
Wallet walletDetails=gson.fromJson(rispostaGetWalletDetails.toString(), Wallet.class);
System.out.println("Balance: "+walletDetails.getBal());
Now when i try to call method on the object i have always null and not the real value.
How i can do?
You have a wrong root level.
Probably, you need to need to get one level down
JSONObject yourObject = json.get("WALLET");
Wallet walletDetails = gson.fromJson(yourObject.toString(), Wallet.class);
To have Gson handle the correct field name mapping while deserializing, you have to register a FieldNamingStrategy like this (using Java 8):
Gson gson = new GsonBuilder()
.setFieldNamingStrategy(field -> field.getName().toUpperCase())
.create();
The strategy will convert each Java field name to match those in your JSON.
This will cover almost all your fields except for those upper-camel-cased in the JSON response, such as "LastName", "CompanyName", etc. In order to map those too, your FieldNamingStrategy will have to become a little bit smarter, like:
field -> {
String fname = field.getName();
return "firstName".equals(fname) || "companyName".equals(fname) /*etc...*/ ? capitalize(fname) : fname.toUpperCase();
}
and so on, I think you got the idea.
The capitalize() method you can find in libraries like Apache Commons Lang or write your own, it's just for examplification here.
Your object variable name doesn't match the json attribute name. "EMAIL" in json should have same EMAIL in object. To overcome this, you could mention #JsonProperty before your attribute declaraction.
for eg:
#JsonProperty("EMAIL")
private String email;
I was trying a simple hibernate populate the db example. There are two POJO's Employee and Address. When I tried to use both the Employee and Address constructor's with parameters to create two instances an error could not get constructor for org.hibernate.persister.entity.singletableentitypersisterwas thrown but the property accessor methods worked fine. Why did I get the error ?
Ok since I do not have the stack trace right now I shall rephrase my question are property accessor methods preferred over constructors in hibernate?
Employee POJO:
package many2one;
public class Employee {
public int id;
public String firstName;
public String lastName;
public int salary;
public Address address;
public Employee(){}
public Employee(String firstName,String lastName,int salary,Address address){
this.firstName = firstName;
this.lastName = lastName;
this.salary = salary;
this.address = address;
}
public int getId(){
return id;
}
public void setId(int id){
this.id = id;
}
public String getFirstName(){
return firstName;
}
public void setFirstName(String fname){
this.firstName = fname;
}
public String getLastName(){
return lastName;
}
public void setLastName(String lname){
this.lastName = lname;
}
public int getSalary(){
return salary;
}
public void setSalary(int salary){
this.salary = salary;
}
public Address getAddress(){
return address;
}
public void setAddress(Address address){
this.address = address;
}
#Override
public String toString(){
return id+","+firstName+","+lastName+","+salary+","+address.getStreetName()+","+address.getCityName()+","+address.getStateName()+","+address.getZipcode();
}
}
Address POJO:
package many2one;
public class Address {
public int id;
public String streetName;
public String cityName;
public String stateName;
public String zipcode;
public Employee employee;
public Address(){
}
public Address(String sname,String cname,String statename,String zipcode){
this.streetName = sname;
this.cityName = cname;
this.stateName = statename;
this.zipcode = zipcode;
}
public int getId(){
return id;
}
public void setId(int id){
this.id = id;
}
public String getStreetName(){
return streetName;
}
public void setStreetName(String streetname){
this.streetName = streetname;
}
public String getCityName(){
return cityName;
}
public void setCityName(String cname){
this.cityName = cname;
}
public String getStateName(){
return stateName;
}
public void setStateName(String statename){
this.stateName = statename;
}
public String getZipcode(){
return zipcode;
}
public void setZipcode(String zipcode){
this.zipcode = zipcode;
}
public Employee getEmployee(){
return employee;
}
public void setEmployee(Employee employee){
this.employee = employee;
}
}
`
Your class should have a default public constructor that does not take any arguments. That's the only constraint with respect to constructors when using Hibernate.
As for the exception, you are probably missing a setter for one of your fields or the setters don't follow the convention expected by Hibernate. But this can only be confirmed if you provide a full stack trace.
are property accessor methods preferred over constructors in
hibernate?
What do you mean by preffered? If you mean are property methods optional, then the answer is no. (Whcih could be one of the reasons for the exception in the first place)