I'm developing a REST API using java and spring boot.
There are two entities with One-to-One relation:
User:
@Entity
@Table(name = "users")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name = "first_name", nullable = false)
private String firstName;
@Column(name = "last_name", nullable = false)
private String lastName;
@Column(name = "email", nullable = false, unique = true)
private String email;
@OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "employee")
@JsonBackReference
private Company company;
// constructors, getters & setters...
And Company:
@Entity
@Table(name = "companies")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name = "company_name", nullable = false)
private String companyName;
@Column(name = "company_info", nullable = false)
private String companyInfo;
@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at", nullable = false)
private Date createdAt;
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "employee_id", nullable = false)
@JsonManagedReference
private User employee;
// constructors, getters & setters...
I want to be able to create Companies via POST method so I've made one in CompanyController (I omit GET methods in controller as they work as expected):
@RestController
@RequestMapping(value = "/api/v1")
public class CompanyController {
private static final Logger logger = LoggerFactory.getLogger(CompanyController.class);
@Autowired
private CompanyRepository companyRepository;
@PostMapping(path = "company", produces = "application/JSON")
public ResponseEntity<?> createCompany(@RequestBody Company company){
logger.info("Request to save new Company: {}", company);
Company result = companyRepository.save(company);
return ResponseEntity.ok().body(result);
}
The createCompany method works fine when I send request with JSON like below:
{
"companyName" : "Big Client Company 2",
"companyInfo" : "unspecified",
"employee" : {
"id": 17,
"firstName": "someName",
"lastName": "someLastName",
"email": "[email protected]"
}
}
However, I want to be able to send JSONs without whole body of employee field but with just an id:
{
"companyName" : "Big Client Company 2",
"companyInfo" : "unspecified",
"employee" : 17
}
When I do like this I get an error:
JSON parse error: Cannot construct instance of
model.User(although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (17)
So the question is there any way to do this without changing Company class "employee" to String (and getting rid of One-to-One relation)?
I've tried to find examples of writing custom JSON deserializers but haven't succeeded with any suitable for me.
2 Answers 2
You can use custom deserialization:
public class CompanyDeserializer extends StdDeserializer<Company> {
@Autowired
private UserRepository userRepository;
public CompanyDeserializer() {
this(null);
}
public CompanyDeserializer(Class<?> vc) {
super(vc);
}
@Override
public Company deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
Company c = new Company();
//Set the rest of the attributes.
Long empId = (Long) ((IntNode) node.get("employee")).numberValue();
c.setEmployee(userRepository.findById(empId).orElse(null)));
return c;
}
}
1 Comment
@JsonComponent annotation and it worked! Thank you!The problem is that your User has mandatory fields that need to be supplied when creating them (firstName, lastName and email).
You have no way to create a User without them using the definitions you have now.
You can use nullable = true on those fields but you might end up with inconsistencies in the data you want to keep in your DB. If letting these fields be empty then I would suggest this approach.
Also, as a side note, please note that mixing the REST controller layer and the repository layer is usually a bad practice. You should separate these layers by a service layer.
2 Comments
Explore related questions
See similar questions with these tags.
"employee" : { "id" : 17 }As for the link I've already checked it before posting myself and it didn't work for me.