Define required fields in POJO class for JSON mapping - java

I am mapping my API JSON response to Java object using ObjectMapper (Jackson). Below is how my json looks like :
[
{
firstKey : "value1",
secondKey : "value2",
thirdKey : "value3"
},
{
firstKey : "value4",
secondKey : "value5",
thirdKey : "value6"
}
]
Required fields are :
firstKey
secondKey
thirdKey
Some of my JSON responses might not have all these three required fields, for which I would like Jackson to throw exception while deserializing. How should I let Jackson know about the required fields ? Is there any annotation for it, except JsonProperty(required = true) since this does not works ?
Also, if a key has null value, it is accepted default value, so I cannot use #NotNull as well. For eg :
[
{
firstKey : null,
secondKey : "value2",
thirKey : "value3"
}
]
Above is valid JSON and should parsed without any exception during deserialization.

Validation functionality overall is not implemented in Jackson since it is considered to be out of scope, see for example Jackson - Required property?.
And some information about why the annotation #JsonProperty(required = true) does not work on field can be found here: Jackson #JsonProperty(required=true) doesn't throw an exception.
However there is a trick that might work for null & existing well-valued fields values but throw an exception if the field is missing completely. Create a constructor with annotation #JsonCreator (and do not create a default constructor!) where the same annotation #JsonProperty(value = "*field_name*", required = true) is used and it will throw in case of missing field, so like:
#Getter #Setter
public class KeyHolder {
private String firstKey;
private String secondKey;
private String thirdKey;
#JsonCreator
public KeyHolder(
#JsonProperty(value = "firstKey", required = true) String firstKey,
#JsonProperty(value = "secondKey", required = true) String secondKey,
#JsonProperty(value = "thirdKey", required = true) String thirdKey) {
this.firstKey = firstKey;
this.secondKey = secondKey;
this.thirdKey = thirdKey;
}
}
With these, doing:
new ObjectMapper().readValue("{ \"firstKey\": \"val1\", \"secondKey\": \"val2\" }"
, KeyHolder.class);
should result into something like:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Missing required creator property 'thirdKey' (index 2)
Any required parameter needs also to be a constructor parameter. So if there is added field fourthKey the constructor needs also a fix so like adding:
#JsonProperty(value = "fourthKey", required = true) String fourthKey) {
and
this.fourthKey = fourthKey;

Related

Spring boot RequestBody getting null

I'm new in Spring Boot, i have a little issue when i'm getting JSON in my RestController from Postman.
When i send the request from Postmat with its attributes it's always getting null in the RequestBody.
This is my Rest controller
#RestController
#RequestMapping(value="/api/enviar",produces = MediaType.APPLICATION_JSON_VALUE,
headers = {"content-type=application/json"})
#RequiredArgsConstructor
#Slf4j
public class EnviarAdicionService {
#Autowired
private final EnviarAdicionUseCase enviarAdicionUseCase;
#Autowired
private final GuardarLogUseCase guardarLogUseCase;
private final Gson gson = new Gson();
private String _statusCode;
private Date dateStart;
#PostMapping
public DatosAdicionResponse PostAdicionFondos(#RequestBody #Valid RequestAdicion requestAdicion){
//PostMapping logic
...
}
}
My RequestAdicion is like this:
#Data
#NoArgsConstructor
public class RequestAdicion implements Serializable {
private final static String regExpression = "\\{\\[sw\\.,ñÑ]\\+\\$}";
#RequestHeaderValidation
private RequestHeader Header;
#CuentaValidation(NumeroIdentificacionName = "NumeroDocumentoCuentaOrigen", TipoIdentificacionName = "TipoDocumentoCuentaOrigen",
TipoCuentaDepositosName = "TipoCuentaDepositoOrigen", NumeroCuentaDepositoName = "NumeroCuentaDepositoOrigen",
EntidadCuentaName = "EntidadCuentaOrigen", TipoCuentaName = "TipoCuentaOrigen", NumeroCuentaName = "NumeroCuentaOrigen",
CodigoFondoName = "CodigoFondoCuentaOrigen", EntidadCuentaDepositoName = "EntidadCuentaDepositoOrigen")
private CuentaOrigen CuentaOrigen;
#CuentaFondoValidation(TipoIdentificacionName = "TipoDocumentoCuentaDestino", NumeroIdentificacionFondoName = "NumeroDocumentoCuentaDestino",
EntidadName = "EntidadCuentaDestino", CodigoFondoName = "CodigoFondoCuentaDestino",
NumeroFondoInversionName = "NumeroFondoInversionCuentaDestino")
private CuentaFondo CuentaDestino;
#FormaDePagoValidation
#Pattern(regexp = regExpression,message = "Valor no permitido. FormaDePago")
private String FormaDePago;
#ValorValidation
private double ValorAdicion;
#OficinaValidation
private long Oficina;
#CanalValidation
#Pattern(regexp = regExpression,message = "Valor no permitido. Canal")
private String Canal;
}
I'm sending these attributes in Postman
Headers
Content-Type: application/json
Body
{
"Header": {
"SystemId": "AW1371",
"MessageId": "1234567890120006",
"UserName": "autWakanda",
"Destination": {
"Name": null,
"NameSpace": null,
"Operation": null
}
},
"CuentaOrigen": {
"NumeroDocumentoCuentaOrigen": 8232166,
"TipoDocumentoCuentaOrigen": "1",
"TipoCuentaDepositoOrigen": "7",
"NumeroCuentaDepositoOrigen": "40673760005",
"EntidadCuentaOrigen": "00007",
"TipoCuentaOrigen": "7",
"NumeroCuentaOrigen": "40673760005",
"CodigoFondoCuentaOrigen": "123"
},
"CuentaDestino": {
"TipoDocumentoCuentaDestino": "1",
"NumeroDocumentoCuentaDestino": 1360740,
"NumeroFondoInversionCuentaDestino": "0021008106434090",
"EntidadCuentaDestino": "00532",
"CodigoFondoCuentaDestino": "21"
},
"FormaDePago": "72",
"ValorAdicion": 133000.31,
"Oficina": 3132,
"Canal": "SVE"
}
I tried to set the first character in lowercase but it doesn't work.
This is the exception raised:
Caused by: java.lang.NullPointerException: Cannot invoke "co.com.bancolombia.model.requestheader.RequestHeader.getSystemId()" because the return value of "co.com.bancolombia.api.models.RequestAdicion.getHeader()" is null
Thank you all in advance.
EDIT: I solved the problem setting the annotation #JsonProperty to each field, and creating to each model a NoArgsConstructor initializing variables with no value and the annotation #AllArgsConstructor in the class.
I just had the same issue for a different reason, so I'm adding this quick note to maybe help someone else.
I couldn't figure why my controller parameter annotated as #RequestBody was coming thru as all blank/empty. Already checked Content-Type: application/json.
I wasted a lot of time on this before realizing my IDE had chosen a totally wrong import for that annotation class.
So just make sure you've got
import org.springframework.web.bind.annotation.*;
and not some other RequestBody class in your imports.
The problem is with your case. AFAIK, by default spring uses camelCase even if you use PascalCase (or UpperCamelCase) for your fields.
I suggest you using camelCase in stead, but if you need to stick to pascal case you should add:
#JsonProperty("Header") on your header field (and do the same for other fields.
you could also tweak the objectMapper to do this without the annotations.

How to get Java's object field's name from JSON fields name

I want to filter out some fields in the response. Filtering should be done before the Java object is serialised into the JSON.
Consider:
public class Entity {
#JsonProperty("some_property")
String someProperty;
#JsonProperty("nested_entity")
#OneToMany(mappedBy = "entity", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
NestedEntity nestedEntity;
// other fields for eg fieldA, fieldB
}
API endpoint
get api/entity/{id}?fields=some_property,field_a
Now the ask is, in the o/p we should filter out only someProperty and fieldA. Like
{
"some_property": "foo",
"field_a": "bar"
}
But since these are JSON fields not Java object fields I can't filter or get these fields them by Reflection. Is there a way we can achieve this, i.e. filtering of Java object based on json fields ?
FYI: The advantage of filtering before serialization is that the lazy-fields' DB calls are saved unless these fields are required
Thanks in advance!
On the suggestion of #robocode using #JsonFilter and also to support empty fields or no fields filtering added JacksonConfiguration
#JsonFilter("entityFilter")
public class Entity {
#JsonProperty("some_property")
String someProperty;
// other fields for eg fieldA, fieldB
}
#Configuration
public class JacksonConfiguration {
public JacksonConfiguration(ObjectMapper objectMapper) {
objectMapper.setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false));
}
}
public class FieldMapper {
#SneakyThrows
public static Dto getFilteredFields(Dto make, String fields[]) {
ObjectMapper objectMapper = new ObjectMapper();
if(ArrayUtils.isNotEmpty(fields)) {
FilterProvider filters = new SimpleFilterProvider().addFilter(
"entityFilter", SimpleBeanPropertyFilter.filterOutAllExcept(fields)
);
objectMapper.setFilterProvider(filters);
} else {
objectMapper.setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false));
}
JsonNode j = objectMapper.readTree(objectMapper.writeValueAsString(make));
return objectMapper.convertValue(j, Dto.class);
}
}
// controller code to get the dto for api/entity/{id}?fields=some_property,field_a
Dto filteredFields = getFilteredFields(dto, fields);
return filteredFields

Creating Json Object Template From Objects Of Objects

I have one JSON which looks something like this
{
"uniqueId":"junk",
"buildingId":"123",
"famousFor":[
{
"famousForId":"asd",
"name":"Gaming",
"activeState":true
}
],
"openHours":[
{
"day":"Sunday",
"timingsFrom":{
"time":"11:00",
"meridian":"AM"
},
"timingsTo":{
"time":"11:59",
"meridian":"PM"
}
}
],
"uploadedImages":{
"coverPhoto":[
{
"imageUrl":"http://google.com/images/a123a.png",
"activeState":false
}
],
"profilePhoto":[
{
"imageUrl":"http://google.com/images/a123a.png,
" activeState":false
}
]
},
"fDescriptions":[
{
"fMapUrl":"http://google.com/images/a123a.png",
"tag":"1"
}
],
"Outlets":[
{
"outletName":"Halo",
"floorNumber":1,
"category":"Gaming"
}
]
}
Now the thing is I have to create one GET API which essentially will provide me the same template with empty value. While returning the Object it's sending me the null value. How can I standardized the template that looks the same. My Object looks something like this.
public class EssentialDetails {
#NotBlank(message=ApplicationUtil.MISSING_FIELD)
#Valid
#Pattern(regexp = "[0-9]+$",message="DP ID Must be Number")
String dpId;
#Id #NotBlank(message= ApplicationUtil.MISSING_FIELD)
#Valid
#Pattern(regexp = "[A-Za-z0-9]+$",message="Must Be Combination of Number and Letters")
String tpId;
#NotNull(message=ApplicationUtil.MISSING_FIELD) #Valid
List<FamousFor> famousFor;
#NotNull(message=ApplicationUtil.MISSING_FIELD) #Valid
List<OpenHours> openHours;
#NotNull(message=ApplicationUtil.MISSING_FIELD) #Valid
Pictures uploadedImages;
#NotNull(message=ApplicationUtil.MISSING_FIELD) #Valid
List<FloorDescription> floorDescriptions;
#NotNull(message=ApplicationUtil.MISSING_FIELD) #Valid
List<Outlets> mallOutlets;
}
How can I pass the empty template with every field present within the template? I'm using java 8 and spring boot 2.0.6.
Incase of empty or null or some valid values below annotation can be used above property of your model
#JsonInclude(ALWAYS)

Converting a Java class with nested array of Java classes to JSON

Is there a convenient way to convert a Java class with a nested array of Java classes into JSON? For instance I want to convert an instance f the following class to JSON:
public class Students {
private final String serial_no;
private final class InnerData {
private final String[] strs;
private final String name;
private final String city;
}
private final StudentList[] students;
}
as
{
"serial_no" : null,
students : [
{
"strs" : ["athlete", "grammarian"],
"name" : "John Smith",
"city" : "Auckland"
},
{
"strs" : ["postmaster", "swimmer"],
"name" : "Jane Doe",
"city" : "Sydney"
}
]
}
What is the best way to do this in Spring? The examples I have come across seem to be simple classes so far with no nesting.
To return an java object in JSON form from an spring objects requires two simple configurations:
1) Adding 'jackson-mapper-asl' dependency to the classpath
2) Add #ResponseBody annotation to the controller's method 
1) Adding 'jackson-mapper-asl' dependency to the classpath
In a spring mvc project we need to add a 'jackson-mapper-asl' dependency to the pom.xml file, and object to json conversion is done by default.
<dependency>   <groupId>org.codehaus.jackson</groupId>   <artifactId>jackson-mapper-asl</artifactId>   <version>1.9.10</version>  </dependency>  
2) Add #ResponseBody annotation to the controller's method
Second thing we need to do is to use '#ResponseBody' annotation against the controller's method. This will make spring understand that method return value should be bound to the web response body.
#RequestMapping("studentlist")   public 
#ResponseBody   List<Student> getStudentList() {    List<Student> studentList = new ArrayList<Student>();    studentList.add(new Student(23, "Meghna", "Naidu", "meghna#gmail.com",      "8978767878"));    studentList.add(new Student(3, "Robert", "Parera", "robert#gmail.com",      "8978767878"));    studentList.add(new Student(93, "Andrew", "Strauss",      "andrew#gmail.com", "8978767878"));    studentList.add(new Student(239, "Eddy", "Knight", "knight#gmail.com",      "7978767878"));      return studentList;   
} 

Saving UnknownFields to a map while deserializing Json using Jackson

My class looks like:
Class A{
private String amount;
#JsonIgnore
private Map<String,String> unknownFields = new HashMap<>();
}
My ObjectMapper have DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES = false configured.
Json input:
{
"amount": 5000,
"note" : "Amount is 5000"
}
In this case I need the note to be in the unknownFields Map:
I am looking for some annotations like
#OnUnknownProperties
public void OnUnknownProperties(String name, String value){
unknownFields.put(name,value);
}
You could annotate a Method in your Domain-Class with #JsonAnySetter (#JsonAnyGetter) and handle it. A good example is here:
http://www.jasonwhaley.com/handling-top-level-metadata-with-jackson/ . Let your DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES=false.

Categories