ReST resource/model with optional fields - java

I am trying to learn how to implement a CRUD ReST API. I created a simple application using JAX-RS and am testing my HTTP methods using Postman. My question is, how do I define optional fields for a model for POST method?
ex. Person contains firstName, lastName as required fields, and age, gender as optional fields.
#XmlRootElement
public class Person {
private long id;
private String firstName;
private String lastName;
public Person() {}
public Person(long id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
//getter and setters omitted
Above is a sub-resource to another resource, and below addPerson service method is used to POST to a HashMap.
public Person addPerson(long userId, Person person) {
Map<Long, Person> persons = users.get(userId).getPersons();
person.setId(persons.size() + 1);
persons.put(person.getId(), person);
return person;
}
I thought of constructing a model whose constructor has multiple different combination of parameters to instantiate a model class, but that doesn't seem very efficient. Can somebody advise?
FURTHER EDIT: This needs to be done while processing JSON. Given two JSON formatted request:
{
"firstName": "Jay",
"lastName": "Kay",
"age": "13"
}
and
{
"firstName": "Jay",
"lastName": "Kay"
}
both should be processable since age(as well as gender) is an "optional attributes" so it may be either omitted, or have a value. I'm coming across JSON parsers and trying to read through them since that may be an answer to processing such requests.

Add gender and age to your Person class
#XmlRootElement
public class Person {
private long id;
private String firstName;
private String lastName;
private int age;
private String gender;
public Person() {}
public Person(long id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
}
While making request from Postman, include the age and gender as keys in x-www-form-urlencoded.
By default, the value of integer is 0 and value for String is empty string.
In your method to handle the request, check the values for gender and age.
If the values are provided in the request, then use the setters of Person class to set the values accordingly.
Suppose the following is your method to handle the request :
#POST
#Path("/AddPerson")
#Produces(MediaType.TEXT_PLAIN)
public String addPerson(#FormParam("id") long id,
#FormParam("firstName") String firstName,
#FormParam("lastName") String lastName,
#FormParam("age") int age,
#FormParam("gender") String gender) {
Person person = new Person(id,firstName,lastName);
if(age != 0) {
person.setAge(age);
}
if(!gender.equals("")) {
person.setGender(gender)
}
// Assuming that PersonService class has the addPerson method
PersonService.addPerson(1,person)
return "Person with id "+ id + " added";
}

Related

How in JPA mappe into one column values of two other columns?

I am using JPA, and in my DB I have a table student with columns: id, firstname and firstAndLastname
So I did an Entity to mappe data, I can get lastname from another table, but I don't persist it as there is no columns lastname in DB.
How to store in column firstAndLastname that is concatenation of firstname and lastname and store it in specific column, example : firstname : John, lastname : Doe => firstAndLastname : JohnDoe
#Entity
#Table(name = "student")
public class Student implements Serializable {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "firstname")
private String firstname;
#Transient
private String lastname;
#Column(name = "firstAndLastname")
private String firstLastname;
I tried this (and not working) :
public String getFirstLastname() {
return this.firstname+ this.lastname;
}
Any idea ?
Genetate getters/setter for all properties of the Entity.
Instantiate the object to save: Student student = new Student(1,"Mario","Rossi");
Call the setter : student.setFirstLastName(student.getFirstName()+student.getLastName());
myStudentService.save(student);.
If you still haven't figured out how to persist data to the database, that's where you need to start. You don't persist data in the JPA itself, that's just the structure for the object. You will build an instance of the Student object in another Class, set the values, and persist it to the database.
You would set that firstLastName value where/when you are creating an instance of your Student.
This is just a rough explanation. Not explicit code for you.
You would have a class like:
public class SaveStudentService {
public saveStudent(){
Student student = new Student();
student.setFirstName("test");
student.setLastName("lastTest");
student.setFirstLast(student.getFirstName + " "+ student.getLastName);
studentRepo.save(student);
}
}
You'll have your normal getters and setters in your Student class...
public Student getfirstName() {return this.firstName;}
public Student setfirstName(String firstName) {return this.firstName = firstName;}
public Student getfirstLast() {return this.firstLastName;}
public Student setfirstName(String firstLastName) {return this.firstLastName = firstLastName;}
I would recommend 2 approaches:
If you instantiate object by constructor:
public Student(final Integer id, final String firstname, final String lastname) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.firstLastname = String.join(firstname, lastname);
}
By setter:
public void setFirstLastname(final String firstName, final String lastName) {
this.firstLastname = String.join(firstName, lastName);
}
In addition you can also use Spring AOP and advice save method on your repository but it's less readable and more difficult to maintenance.
#Before("execution(* com.package.StudentRepository.save(..))")
public void updateFirstLastName(final JoinPoint joinPoint) {
final Object[] arguments = joinPoint.getArgs();
// find Student.class object and update firstLastName field
}
Either way - don't perform concatenation outside Student class because it leads to boilerplate and it's harder to maintenance.
But IMHO the best approach is to store firstName and lastName separately in database and use SQL CONCAT function if needed.

Jackson, deserialize plain JSON array to a single Java object

An external service is providing a JSON array with plain/primitive elements (so without field names, and without nested JSON objects). For example:
["Foo", "Bar", 30]
I would like to convert this to an instance of the following Java class using Jackson:
class Person {
private String firstName;
private String lastName;
private int age;
Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
}
(This class can be adapted if needed.)
Question: is it possible to deserialize this JSON to Java using something like this?
Person p = new ObjectMapper().readValue(json, Person.class);
Or is this only possible by writing a custom Jackson deserializer for this Person class?
I did try the following, but that didn't work:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class Person {
private String firstName;
private String lastName;
private int age;
#JsonCreator
public Person(
#JsonProperty(index = 0) String firstName,
#JsonProperty(index = 1) String lastName,
#JsonProperty(index = 2) int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public static void main(String[] args) throws IOException {
String json = "[\"Foo\", \"Bar\", 30]";
Person person = new ObjectMapper().readValue(json, Person.class);
System.out.println(person);
}
}
Result: Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Argument #0 of constructor [constructor for Person, annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=#com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] has no property name annotation; must have name when multiple-parameter constructor annotated as Creator
at [Source: (String)"["Foo", "Bar", 30]"; line: 1, column: 1]
You don't need #JsonCreator, just use #JsonFormat(shape = JsonFormat.Shape.ARRAY)
#JsonFormat(shape = JsonFormat.Shape.ARRAY)
public static class Person {
#JsonProperty
private String firstName;
#JsonProperty
private String lastName;
#JsonProperty
private int age;
}
And use #JsonPropertyOrder({"firstName", "lastName", "age" } ) if you need to preserve some alternative field declaration order in your bean.

Gson - Read a value with two different keys

In my Android project I have two types of response where both response are identical except two keys.
Response 1
{"fullName":"William Sherlock Scott Holmes","address":"221B Baker Street, London, England, UK","downloads":642,"rating":3,"repos":["https://link1","https://link2","https://link3"]}
Response 2
{"name":"Sherlock","city":"London","downloads":642,"rating":3,"repos":["https://link1","https://link2","https://link3"]}
If you see the responses only two key names are changing fullName/name and address/city
I don't want to create one more pojo for other response. My question is: is it possible to use only one Pojo to read both responses?
public class AccountInfo {
private String name;
private String city;
//other objects
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
//other setters and getters
}
Any help will be appreciated...
You can annotate the members to accept values from two different json names using the #SerializedName annotation:
#SerializedName(value = "name", alternate = {"fullName"})
private String name;
#SerializedName(value = "city", alternate = {"address"})
private String city;
Either named element can then be placed into the members that are annotated like this.
UPDATED :
#SerializedName alternate names when deserializing is added in Version 2.4
Yes, you can totally use one POJO class for deserializing both responses. Your POJO class will contain keys from both responses.
public class Response {
private String name;
private String city;
private String fullName;
private String address;
private Integer downloads;
private Integer rating;
private List<String> repos ;
}
But when using the Response class, be careful that for first response, the name and city will be null, and for the second one, the address and fullname.
Yeah you can do that in a single POJO. Try this:
public class POJO {
#SerializedName("name")
public String name;
#SerializedName("city")
public String city;
#SerializedName("fullName")
public String fullName;
#SerializedName("address")
public String address;
#SerializedName("downloads")
public Integer downloads;
#SerializedName("rating")
public Integer rating;
#SerializedName("repos")
public List<String> repos = new ArrayList<String>();
}
While parsing you have to check values for null. For eg -
While Parsing Response 1: name and city variables will be null
While Parsing Response 2: fullname and address will be null
Note : Try checking values for null before using else you'll get nullpointerexception
Define all possible fields in your POJO Class like
public class AccountInfo {
private String name;
private String city;
private String fullname;
private String address;
}
While performing operation check for null in those feilds

Java trouble serializing a field of an object

I'm trying to learn about serialization and encountered the following problem:
I have an implementation of a customer that looks somewhat like this.
private static customerCount = 0;
private String customerID;
private String name;
private String street;
private String city;
private String postcode;
private String type;
I'm trying to serialize / deserialize an Arraylist
In the constructor, the ID will be created like this:
private Customer(...){
this.customerID = "ID" + customerCount;
customerCount++;
}
The serialization process works, however, all the IDs are set to ID0 when I deserialize.
Can anyone help resolve this problem?
Update: Alright, I just found out that static fields wont be serialized. How can I "model" the ID of a customer so I can serialize it? I need to have a unique value to create IDs for customers.
Here's a solution that combines the factory with the list that keeps track of customer count.
The customer class has a protected constructor, forcing you to build them through another means within the same package.
public class Customer implements Serializable {
private String customerID;
private String name;
private String street;
private String city;
private String postcode;
private String type;
protected Customer(String customerID,
String name,
String street,
String city,
String postcode,
String type) {
this.customerID = customerID;
this.name = name;
this.street = street;
this.city = city;
this.postcode = postcode;
this.type = type;
}
}
Now within the package, create a list wrapper like this:
public class CustomerList {
private int customerCount = 0;
private List<Customer> customers = new ArrayList<>();
public boolean addCustomer(String name,
String street,
String city,
String postcode,
String type) {
Customer customer = new Customer("ID" + customerCount++,
name,
street,
city,
postcode,
type);
return customers.add(customer);
}
}
This class then takes care of constructing the new customer, and provides a unique ID.
Edit: Just noticed that you now also have the upside of making the CustomerList class serializable as well. Then you can load it and still have an accurate customer count for adding additional uniquely ID-ed customers.
Usually, you would like to serialize only attributes and their values, not the logic from the class. Logic should happen before serialization or after deserialization.

Facing issues when using #JsonCreator with nested Json objects

I have some old code which is working fine but now i want to use #JsonCreator in domain objects and i am able to re-factor all stuff but i am now stucked in one issue. In one scenario i have Json Structure like:
[{
"name" : "John",
"emailAddress" :"stucked#gmail.com"
}]
But in java backend i have structure like:
class Employee {
public final String name;
public final EmailAddress emailAddress;
Employee(final String name, final EmailAddress emailAddress) {
this.name = name;
this.emailAddress = emailAddress;
}
Employee() {
this(null, null)
}
}
class EmailAddress {
public final String address;
EmailAddress(final String address) {
this.address = address;
}
EmailAddress() {
this(null);
}
}
So I am trying like :
class Employee {
public final String name;
public final EmailAddress emailAddress;
#JsonCreator
Employee(#JsonProperty("name")final String name,
#JsonProperty("emailAddress")final EmailAddress emailAddress) {
this.name = name;
this.emailAddress = emailAddress;
}
}
class EmailAddress {
public final String address;
#JsonCreator
EmailAddress(#JsonProperty("address")final String address) {
this.address = address;
}
}
Now the problem is in using #JsonProperty with "emailAddress" field in Employee domain object and then #JsonProperty inside EmailAddress Object with "address" because in Json i dont have any "address" property and due to which JsonParsing error is coming.
Well problem can be easily solved if i declare emailAddress as String instead of EmailAddress and remove EmailAddress object but address field of EmailAddress object is used at many other places, modification of which require lot of effort and there are lots of such cases. Is there any other solution??
So what exactly is your problem here? If the goal is to allow both of these:
"stucked#gmail.com"
{ "address" : "stucked#gmail.com" }
it should already work: Jackson figures out that single-String constructor may be used if JSON String is found; and setters/fields if a JSON Object is found. You just need to ensure names match ("address" vs "emailAddress").

Categories