Jackson Field filter in Spring MVC - java

I have a spring boot application and I need to filter response body from RequestParam
Example :
// DTO
public class PersonDTO
{
private Long id;
private String firstName;
private String lastName;
}
// Controller
public class PersonController
{
#GetMapping(value = "/person")
public ResponseEntity<List<PersonDTO>> getPerson(#RequestParam(required = false) String filters)
{
List<PersonDTO> personList = myservoce.getPerson();
return new ResponseEntity<List<PersonDTO>>(personList, HttpStatus.OK);
}
}
Example of client query:
return all person without fields filter
http://localhost:8080/person
[
{
"id": 123,
"firstName": "toto1",
"lastName": "titi2"
},
{
"id": 345,
"firstName": "toto2",
"lastName": "titi2"
}
]
return all person and the response contain just firstName and lastName:
http://localhost:8080/person?filters=firstName,lastName
[
{
"firstName": "toto1",
"lastName": "titi2"
},
{
"firstName": "toto2",
"lastName": "titi2"
}
]
I have found this API "jackson-dynamic-filter", but the filter is used as annotation like this :
public class PersonController
{
#FilterOutAllExcept({"firstName", "lastName"})
#GetMapping(value = "/person")
public ResponseEntity<List<PersonDTO>> getPerson( #RequestParam(required = false) String filters )
{
List<PersonDTO> personList = myservoce.getPerson();
return new ResponseEntity<List<PersonDTO>>(personList, HttpStatus.OK);
}
}
in my case I cannot use this API because the list of field filter are managed by the client and it can be different for each call and my real payload Dto contain a lot of field
I have found also this API "jackson-antpathfilter" but it not work for me and also the response type is MappingJacksonValue and not a ResponseEntity>
Any idea how I can configure this use case with spring application ?

I have found temporary this solution :
#ControllerAdvice
public class JsonFilterAdvice implements ResponseBodyAdvice<List<?>>
{
#Override
public List<?> beforeBodyWrite(
List<?> arg0,
MethodParameter arg1,
MediaType arg2,
Class<? extends HttpMessageConverter<?>> arg3,
ServerHttpRequest arg4,
ServerHttpResponse arg5)
{
HttpServletRequest servletRequest = ((ServletServerHttpRequest) arg4).getServletRequest();
String[] params = servletRequest.getParameterValues("filters");
if (params != null)
{
// parse object and set field to null
}
return arg0;
}
#Override
public boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1)
{
// return true if method parameters contain 'filters' field
return true;
}
any other suggestions are welcome

This is from my web service, you can use this code and adapt to your model and repository. From this you can create a generic service and call your modified version wherever you need it.
#RequestMapping(value = "/entidades/{id}/campos", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#ApiOperation(value = "Retrieves requested object fields", response = Entidade.class)
public ResponseEntity<Map<String, Object>> getFields(#Valid #PathVariable Long id, #RequestParam String campos) {
final Optional<Entidade> ent = entidadeRepository.findById(id);
final String[] camposArr = campos.split(",");
if (ent.isPresent()) {
final Entidade e = ent.get();
Map<String, Object> result = new HashMap<>();
for (String campo : camposArr) {
String methodName = "get" + (campo.substring(0, 1).toUpperCase() + campo.substring(1));
// System.out.println(campo);
try {
Method method = e.getClass().getMethod(methodName);
// System.out.println(method.invoke(e));
result.put(campo, method.invoke(e));
} catch (Exception err) {
}
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}

You can add annotation #JsonIgnore of Jackson lib to filter field id:
public class PersonDTO implements Serializable
{
#JsonIgnore
private Long id;
private String firstName;
private String lastName;
}

Related

Get the value of items of a Json with Java Spring Boot

I'm trying to extract API data from the Json file below.
I want to retrieve the "name" of each "item".
Once the "name" is retrieved, I want to create a new Json that will contain :
{name: "toto", name: "titi"....}
The goal is then to create an API on my side which on a call from http://localhost/getitems will return the result of the Json created.
I'm new to Java and Spring Boot, so if you think there is a code that is easier, let me know, i hope you can help me to create that new Json file easily. Thanks !
// Json File (it has been reduced, more than 700 name are present)
{
"kind": "Space",
"apiVersion": "v1",
"metadata": {
"selfLink": "something",
"resourceVersion": "something"
},
"items": [
{
"metadata": {
"name": "projet1"
}
},
{
"metadata": {
"name": "com-cicd"
}
}
]
}
// TestGet.java Class
public static NameSpaceJson getPostWithCustomHeaders(String DebutUrl, String MilieuUrl, String ParamUrl) {
String url = DebutUrl.concat(MilieuUrl).concat(ParamUrl);
String Bearer = "...";
// create headers & template
HttpHeaders headers = new HttpHeaders();
RestTemplate restTemplate = new RestTemplate();
// set `accept` header for the type of response
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
// set custom header, bearer here too
headers.set("x-request-source", "desktop");
headers.set("Authorization", "Bearer "+Bearer);
// build the request
#SuppressWarnings({ "rawtypes", "unchecked" })
HttpEntity request = new HttpEntity(headers);
// use `exchange` method for HTTP call, this one permits us to use bearer for auth
ResponseEntity<NameSpaceJson> response = restTemplate.exchange(url, HttpMethod.GET, request, NameSpaceJson.class, 1);
if(response.getStatusCode() == HttpStatus.OK) {
return response.getBody();
} else {
return null;
}
}
// The name in the same file
public static void main(String[] args) {
TestGet.disableSSLCertificateChecking();
NameSpaceJson resultresponse = getPostWithCustomHeaders("https...","api","names");
// Long response = resultresponse.getValue().getId();
List<Item> response = resultresponse.getItems();
String test = GenerateNewJsonNameSpace.createJsonContent(response);
System.out.println(test);
}
//NameSpaceJson.java File
package com.example.consumingrest;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class NameSpaceJson {
private String kind;
private String apiVersion;
private List<Item> items;
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public String getApiVersion() {
return apiVersion;
}
public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
public List<Item> getItems() {
return items;
}
public void setItems(List<Item> items) {
this.items = items;
}
}
//Metadata.java
package com.example.consumingrest;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Metadata {
private String name;
private String creationTimestamp;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCreationTimestamp() {
return creationTimestamp;
}
public void setCreationTimestamp(String creationTimestamp) {
this.creationTimestamp = creationTimestamp;
}
}
//Item.java
package com.example.consumingrest;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Item {
Metadata metadata;
public Metadata getMetadata() {
return metadata;
}
public void setMetadata(Metadata metadata) {
this.metadata = metadata;
}
}
// GenerateNewJsonNameSpace ( this is what i have tried.. but i'm sure we can do really better.. )
package com.example.consumingrest;
import java.util.List;
public class GenerateNewJsonNameSpace {
public static String createJsonContent(List<Item> ListOfNameSpace) {
if(ListOfNameSpace.isEmpty()) {
return null;
}else {
String LeJson;
LeJson = "{";
for(int i = 0; i < ListOfNameSpace.size(); i++) {
LeJson.concat(ListOfNameSpace.get(i).getMetadata().getName());
LeJson.concat(", \n");
}
LeJson.concat("}");
return LeJson;
}
}
}
you can use a library named Gson, which is created by google specifically for handling the JSON data.
All you need to do is create a new Gson object and parse the JSON with it.
You can do in just couple of lines
String jsonString = "{ \"kind\": \"Space\", \"apiVersion\": \"v1\", \"metadata\": { \"selfLink\": \"something\", \"resourceVersion\": \"something\" }, \"items\": [ { \"metadata\": { \"name\": \"projet1\" } }, { \"metadata\": { \"name\": \"affeccom-cicd\" } } ] }";
JsonObject data = new Gson().fromJson(jsonString, JsonObject.class);
JsonArray names = data .get("items").getAsJsonArray();
for(JsonElement element : names){
JsonObject object = element.getAsJsonObject();
System.out.println(object.get("metadata").getAsJsonObject().get("name").getAsString());
}

How to add a Rest validation in array property of an object?

I need to validate the request msisdn array type, I'm using rest validation of spring boot and how can I add the value in error message too.
I tried custom validation but I cannot find any array validation.
Desired error response
{
"errors": [
"Invalid msisdn: 0917854*****",
"Invalid msisdn: 0936895*****"
],
"success": false,
}
Request Body
{
"msisdn": ["0917854*****", "0936895*****"],
"message": "test message",
"title": "test title"
}
java object
public class PushNotif {
//validate this List
private List<String> msisdn;
#NotNull(message = "Message is required")
private String message;
private String title;
public PushNotif(List<String> msisdn, String message, String title) {
this.msisdn = msisdn;
this.message = message;
this.title = title;
}
}
java controller
#RestController
public class PushController extends BaseController{
#PostMapping(path = "/push")
public ResponseEntity<Object> indexAction(#valid #RequestBody PushNotif pushNotif){
return new ResponseEntity<Object>(null,HttpStatus.ok);
}
}
Error Response Handler
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler
{
#Override
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
Map<String, Object> errors = new HashMap<>();
List<String> details = new ArrayList<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
details.add(error.getDefaultMessage());
});
errors.put("details", details);
errors.put("success", false);
errors.put("traceid", "aksjdhkasjdhs-akjsdjksa-asjkdh");
return new ResponseEntity<>(errors, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
UPDATE: I added Parameter constraints to my java object
#NotNull(message = "msisdn is required")
private List<#NotNull(message = "msisdn is required")
#Pattern(regexp = "^09[0-9]{9}",
message = "Invalid msisdn ${validatedValue}") String> msisdn;
In order to validate values in a list, in this case values not null, you can add the #NotNull annotation in object reference type.
private List<#NotNull String> msisdn;
References
Hibernate Validations for Nested Container Elements

Spring Boot - JSON Object Array to Java Array

I have an endpoint in spring boot that consumes this JSON as an example:
{
"userId": 3,
"postBody": "This is the body of a post",
"postTitle": "This is the title of a post",
"created": null,
"tagList": ["tag1", "tag2", "tag3"]
}
The endpoint:
#RequestMapping(value="/newPost", method = RequestMethod.POST, produces="application/json", consumes = "application/json")
#ResponseBody
public ResponseEntity newPost(#RequestBody Map<String, Object> body) throws Exception {
I know the issue here is the Request body is being saved as a Map of objects which is fine for all the other attributes except the tagList. How can I get tagList to be an array of Strings in Java?
Thanks.
A mixutre of Ankur and Jose's answers solved this, thanks for the fast responses guys!
You should probably create a Java class which represents the input JSON and use it in the method newPost(.....). For example:-
public class UserPostInfo {
private int userId;
private String postBody;
private String postTitle;
private Date created;
private List<String> tagList;
}
Also, include the getter/setter methods in this class.
If you want to modify the behavior of JSON parsing, you can use Annotations to change field names, include only non-null values, and stuff like this.
If you don't want to use a custom POJO you could also just handle the deserialization into a Map yourself. Just have your controller accept a String and then use Jackson's ObjectMapper along with TypeReference to get a map.
#RequestMapping(value="/newPost", method = RequestMethod.POST, produces="application/json", consumes = "application/json")
#ResponseBody
public ResponseEntity newPost(#RequestBody String body) throws Exception {
ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<String,Object>> typeRef = new TypeReference<HashMap<String,Object>>() {};
HashMap<String,Object> map = mapper.readValue(body, typeRef);
}
The resulting HashMap will use an ArrayList for the tag list:
You can create a custom Java POJO for the request that uses String[] versus List<String>. Here I did it for you using the site jsonschema2pojo.
package com.stackoverflow.question;
import com.fasterxml.jackson.annotation.*;
import java.util.HashMap;
import java.util.Map;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"userId",
"postBody",
"postTitle",
"created",
"tagList"
})
public class MyRequest {
#JsonProperty("userId")
private int userId;
#JsonProperty("postBody")
private String postBody;
#JsonProperty("postTitle")
private String postTitle;
#JsonProperty("created")
private Object created;
#JsonProperty("tagList")
private String[] tagList = null;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("userId")
public int getUserId() {
return userId;
}
#JsonProperty("userId")
public void setUserId(int userId) {
this.userId = userId;
}
#JsonProperty("postBody")
public String getPostBody() {
return postBody;
}
#JsonProperty("postBody")
public void setPostBody(String postBody) {
this.postBody = postBody;
}
#JsonProperty("postTitle")
public String getPostTitle() {
return postTitle;
}
#JsonProperty("postTitle")
public void setPostTitle(String postTitle) {
this.postTitle = postTitle;
}
#JsonProperty("created")
public Object getCreated() {
return created;
}
#JsonProperty("created")
public void setCreated(Object created) {
this.created = created;
}
#JsonProperty("tagList")
public String[] getTagList() {
return tagList;
}
#JsonProperty("tagList")
public void setTagList(String[] tagList) {
this.tagList = tagList;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}

Send json using jackson and spring boot

I need to send a json to a web address where this json will be consumed, basically I have a list of Dto's that I need to turn into a Json (with jackson).
Some information must be passed in the header:
timestamp= time, key= blablabla, accesskey= bla bla bla
As I have no experience with spring, i need to know how to do the HTTP request using Spring boot
(is it a post?)
Here's what I've implemented so far:
Student Dto Class
public class StudentDto {
private String name;
private String RM;
private String RG;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRM() {
return RM;
}
public void setRM(String RM) {
this.RM = RM;
}
public String getRG() {
return RG;
}
public void setRG(String RG) {
this.RG = RG;
}
}
Turning my list of students into json
public String convertToJson(List obj) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
return jsonInString;
}
}
And this is the format json has to leave
{
"table": "student",
"rows":
[
{
"PersonID": 1,
"name": "Name",
"RM": "56656565",
"RG": "8787845-7",
},
{
"PersonID": 2,
"name": "Name",
"RM": "56656565",
"RG": "8787845-7"
}
]
}
below is and example of how to use rest template. You'll want to create a class for the request that has String table; and List<StudentDto> rows; as members
private static void createEmployee()
{
final String uri = "http://localhost:8080/springrestexample/employees";
MultiValueMap<String> headers = new MultiValueMap<>();
//set headers
HttpEntity<EmployeeVO> newEmployee = new HttpEntity<>(new EmployeeVO(-1, "Adam", "Gilly", "test#email.com"),headers);
RestTemplate restTemplate = new RestTemplate();
EmployeeVO result = restTemplate.postForObject( uri, newEmployee, EmployeeVO.class);
System.out.println(result);
}
*edit added headers
*edid look here for json formatting
you will also need to have the id field added to your object

Deserialize json ids to list of objects

I am sending ajax json request to my controller using jackson. This is my entity:
#Entity
public class Template implements Serializable
{
private String templateName;
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
private List<Action> actions;
//getters setters
}
My JSON looks like:
"{"templateName":"aaa",
"actions":["2", "3"]
}"
Controller:
#RequestMapping(value = "/testCreate", consumes = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody List<ObjectError> testCreate(#Valid #RequestBody final TemplateForm templateForm,
final BindingResult bindingResult)
{
if (bindingResult.hasErrors())
{
return bindingResult.getAllErrors();
}
else
{
//some actions
return EMPTY_LIST;
}
}
How to map action ids from JSON on list of Action object? Thank you.
You can use #InitBinder in case you are using Spring.
Like this:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(ArrayList.class, "actions",
new ActionEditor(actionService));
}
and ActionEditor will look like:
public class ActionEditor extends PropertyEditorSupport {
private final ActionService actionService;
public ActionEditor(ActionService actionService) {
this.ActionService = actionService;
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
List<Action> facilities = new ArrayList<Action>();
String[] ids = text.split(",");
Set<Long> actionIds = new HashSet<Long>();
for (String id : ids) {
actionIds.add(Long.parseLong(id));
}
facilities.addAll(actionService.list(actionIds));
setValue(facilities);
}}

Categories