Spring boot RequestBody with object fields that could be needed - java

How can I make my RequestBody accept a model with object fields that may or may not be included?
This is the Restcontroller endpoint I’m calling
#PatchMapping(value = "/projects/update/{projectNumber}")
public ResponseEntity<Object> updateProject(#PathVariable int projectNumber, #RequestBody RequestReviewUpdate rru)
{
return reviewUpdateService.updateReview(projectNumber, rru);
}
Here is the model #RequestBody is accepting
public class RequestReviewUpdate
{
private UpdateProject project;
private List<UpdateHlsd> reviews;
private List<UpdateProjectLeadership> stakeHolders;
public UpdateProject getProject()
{
return project;
}
public List<UpdateHlsd> getReviews()
{
return reviews;
}
public List<UpdateProjectLeadership> getStakeHolders()
{
return stakeHolders;
}
}
I want to be able to send a JSON could contain all object field, or some.
My JSON body could look something like this...
{
"project": {
"type": "HLSD"
},
"reviews": [
{
"id": 570,
"requestedBy": "Name here",
"notes":"test"
}
],
"stakeHolders": [
{
"id": 1088,
"projectResource": "sid"
}
]
}
Or this...ect
{
"reviews": [
{
"id": 570,
"requestedBy": "name",
"notes":"test"
}
}
Or any other possible combinations.

Just make your RequestReviewUpdate optional, like:
#PatchMapping(value = "/projects/update/{projectNumber}")
public ResponseEntity<Object> updateProject(#PathVariable int projectNumber, #RequestBody(required=false) RequestReviewUpdate rru) {
return reviewUpdateService.updateReview(projectNumber, rru);
}
This will make your parameter optional, either entirely or partially. I.e. any member of the object you receive as an argument, might be present or absent.

Related

Spring reactive returns scanAvailable and prefetch instead of object

I have a REST API developed with Spring Reactive as below and when it returns the response it does have scanAvailable and prefetch instead of object details. We have individually APIs for the used methods below findAllEmployment, getAllWorkerAddressDetailsByWorkerId and it do return the proper response. Not sure what is wrong when I have combined them.
WorkerDTO.java
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Data
#ToString(includeFieldNames = true)
public class WorkerDTO {
...
private Flux<WorkerAddressDTO> workerAddress;
}
ServiceImpl.java
#Override
public Flux<WorkerDTO> method() {
Flux<EmploymentDTO> employmentDTOFlux = findAllEmployment();
Flux<WorkerDTO> workerDTOFlux = getWorkerDetailsWithEmploymentStatus(employmentDTOFlux);
return workerDTOFlux.flatMap(workerDTO -> {
workerDTO.setWorkerAddress(getAllWorkerAddressDetailsByWorkerId(workerDTO.getWorkerId()).map(workerAddressDTO -> workerAddressDTO));
return Flux.just(workerDTO);
});
}
Current Response:
[
{
"xyz": "abc",
"workerAddress": {
"scanAvailable": true,
"prefetch": -1
}
},
{
"xyz": "qwe",
"workerAddress": {
"scanAvailable": true,
"prefetch": -1
}
}
]
Expected Response:
[
{
"xyz": "abc",
"workerAddress": {
"key1": "value1",
"key2": "value2"
}
},
{
"xyz": "qwe",
"workerAddress": {
"key1": "value1",
"key2": "value2"
}
}
]
You need to do some changes. First, drop Flux inside WorkerDTO and replace it with List as follows:
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Data
#ToString(includeFieldNames = true)
public class WorkerDTO {
...
private List<WorkerAddressDTO> workerAddress;
}
Then, you need to change your ServiceImpl code as follows:
#Override
public Flux<WorkerDTO> method() {
Flux<EmploymentDTO> employmentDTOFlux = findAllEmployment();
Flux<WorkerDTO> workerDTOFlux = getWorkerDetailsWithEmploymentStatus(employmentDTOFlux);
return workerDTOFlux.flatMap(workerDTO -> {
return getAllWorkerAddressDetailsByWorkerId(workerDTO.getWorkerId()).collectList()
.map(workerAddresses -> workerDTO.setWorkerAddress(workerAddresses));
});
}

Jackson: Deserializing a polymorphic list

I would like to implement a custom deserializer for our REST API that is not only used by Java application. Therefore I don't want to have Jackson putting type information into the serialized JSON.
I'm currently struggling with deserializing CollectionExpand since it contains a list data of specific ResourceModel.
public class EntityModel<R extends ResourceModel> implements Serializable {
private R data;
private List<ResourceLink> links;
private List<CollectionExpand> expands;
}
public class CollectionExpand {
private String name;
// Resource Model is an interface
private Collection<ResourceModel> data;
}
ResourceModel is an interface an each CollectionExpand contains a collection of one type of ResourceModel per name.
For example a json output could look like this.
{
"data": {},
"links": [],
"expand": [
{
"name": "photos",
"data": [
{
"id": 12,
"name": "hello.jpg"
},
{
"id": 12,
"name": "hello.jpg"
}
]
},
{
"name": "persons",
"data": [
{
"id": 783378,
"name": "Peter",
"age": 12
},
{
"id": 273872,
"name": "Maria",
"age": 77
}
]
}
]
}
As you can see each name contains the same type of resource model. photos contains PhotoResourceModel and person contains PersonResourceModel.
I started to implement my custom Jackson Deserializer
public class CollectionExpandDeserializer extends StdDeserializer<CollectionExpand> {
public CollectionExpandDeserializer() {
super(CollectionExpand.class);
}
#Override
public CollectionExpand deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
CollectionExpand collectionExpand = new CollectionExpand();
if (Objects.equals(p.nextFieldName(), "name")) {
collectionExpand.setName(p.nextTextValue());
}
if (Objects.equals(p.nextFieldName(), "data")) {
// depending on the field name I would like to delegate the deserialization to a specific type.
if (name.equals("photos") {
// how to do this?
collectionExpand.setData(/* deserialize to a list of PhotoResource */);
}
}
return collectionExpand;
}
I'm current stuck on how can I delegate telling Jackson to deserialize this as a PhotoResource list.
In general is this the right approach or is there another way to do it (without putting any Jackson meta data into the JSON while serialization)?
I have ended up implementing my custom deserializer as below
#Override
public CollectionExpand deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
JsonNode node = ctx.readTree(p);
CollectionExpand collectionExpand = new CollectionExpand();
collectionExpand.setName(node.get("name").asText());
ArrayNode data = node.withArray("data");
Iterator<JsonNode> iterator = data.iterator();
while (iterator.hasNext()) {
Class<? extends ResourceModel> aClass = resolveClass(collectionExpand.getName());
if (aClass != null) {
JsonNode jsonNode = iterator.next();
collectionExpand.getData().add(p.getCodec().treeToValue(jsonNode, aClass));
}
}
return collectionExpand;
}
private Class<? extends ResourceModel> resolveClass(String name) {
if ("contents".equals(name)) {
return ContentsGetResourceModel.class;
} else if ("tags".equals(name)) {
return TagsGetResourceModel.class;
} else {
return null;
}
}
I took my a while to understand how to deserialize a JsonNode/TreeNode into a specific type. In the end I learned that this can be basically done by using the parsers codec.
PhotoResource photoResource = p.getCodec().treeToValue(jsonNode, PhotoResource.class);

Parse a nested dynamic module using Gson

I have a JSON that looks more or less like this:
{
"modules": {
"someExistingModule": {
"name": "pug",
...
},
"randomExistingModule": {
"type": "cat",
...
},
"myNewModule": { // <----- I care about this module. Note that this is NOT an array
"modules": {
"img1": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"img2": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"img3": {
"type": "image",
"url": "https://some/random/image,
"altText": "Some image description
},
"txt1": { // <------ Updated JSON
"type": "text",
"content": "Hello world 1"
},
"txt2": {
"type": "text",
"content": "Hello world 2"
},
...
}
}
Inside myModule there can be N number of imgN objects and txtN. I need to parse this dynamically.
My current Response class looks like this:
public class MyModuleResponse extends SomeResponseClass
{
#Override
public void parse(InputStream data)
{
T responseBody = readJsonStream(data, MyModuleResponseBody.class());
MyModuleDataParser.parse(responseBody);
}
MyModuleDataParser.java
...
public static MyModuleDataParser parse(#Nullable MyModuleResponseBody body)
{
parseSomeExistingModule();
parseRandomExistingModule();
parseMyNewModule(); // <--- This is the new module I'm trying to parse. Currently, this method is empty.
}
MyModuleResponseBody.java
public class MyModuleResponseBody
{
public Modules modules;
public static class Modules
{
SomeExistingModule someExistingModule;
RandomExistingModule randomExistingModule;
MyNewModule myNewModule; // <----- My new module
}
public static class SomeExistingModule
{
String name;
...
}
public static class RandomExistingModule
{
String type;
...
}
public static class MyNewModule
{
public ??? modules; // <--- Trying to define the Type here. Something like List<MyImageModule>. But, it won't work
}
MyImageModule.java
public class MyImageModule extends Module // <---- Update: This class now extends a generic Module class
{
private String url;
private String altText;
}
MyTextModule.java <---- New Module
public class MyTextModule extends Module // New class
{
private String content;
}
Module.java
public class Module // <----- New parent class
{
protected String type;
}
How do I create a list of MyImageModule from myNewModule? I believe I need to use some kind of TypeAdapter from Gson library. But, I'm not familiar how to do this inside an existing response.
Use Map<String, MyImageModule>, in fact, a hashmap to solve the issue of non-list modules object in the json.
public static class MyNewModule {
public Map<String, MyImageModule> modules; // initialize as a hashmap
}

Customize JSON view with Jackson for Dynatable

I am working on a Spring Boot application. In the html view, I make a ajax call to a RestController, which returns a list of custom entities:
#Controller
public class MyController {
#ResponseBody
#JsonView(View.MyView.class)
public List<CustomEntity> getEntities() {
...
}
}
This is working fine, I am getting, as expected, following structure:
{
"id": "1",
"name": "Test1"
},
{
"id": "2",
"name": "Test2"
}
In the view, I want to use it with Dynatable. And here comes my problem. I need following structure:
{
"records": [
{
"id": "1",
"name": "Test1"
},
{
"id": "2",
"name": "Test2"
}
],
"queryRecordCount": 2,
"totalRecordCount": 2
}
Is there a way to generate the JSOn view using jackson (or any other framework) based on a template, so I can use the data with Dynatable, and if so, how?
Thanks in advance,
Stephan
You could create a wrapper that does this for you...
class DyntableResponse<T> {
private List<T> records;
public List<T> getRecords() { return records; }
public void setRecords(final List<T> records) { this.records = records; }
public int getQueryRecordCount() { return records.size(); }
public int getTotalRecordCount() { return records.size(); }
}
And then return it from your RestController...
#Controller
public class MyController {
#ResponseBody
#JsonView(View.MyView.class)
public DyntableResponse<CustomEntity> getEntities() {
final DyntableResponse<CustomEntity> resp = new DyntableResponse<>();
resp.setRecords(...); // Your finder here.
return resp;
}
}
This is untried, but should be close.

Using RoboSpice/Retrofit with Parametrized types for JSON Response

Help please T_T been digging for 2 days for a solution to this problem!
I am using Retrofit to make some networking tasks in my app,
One of the APIs that I am calling returns a different object sometimes, but i know when and what it will return everytime I call it.
In this case the Message is an object
{
"key": "some key",
"category": "some category",
"channel": "some channel",
"status": "some status",
"message": {
"someValue": "54",
"someOtherValue": "5353"
}
}
and here the Message is a string
{
"key": "some key",
"category": "some category",
"channel": "some channel",
"status": "some status",
"message": "this is a string"
}
so I am trying to achieve a good design of this solution by using generics,
I have a generic class like this,
ContentResponse Class
public class ContentResponse<T> {
private List<Content<T>> content = new ArrayList<Content<T>>();
//getters and setters
}
Content Class
public class Content<T>
{
private String key;
private String category;
private String channel;
private String status;
private T message;
//getters and setters
}
ContentInterface Class
public interface ContentInterface<T>
{
#GET("/public/content/{category}")
ContentResponse<T> getContent(#Path(PathParameters.CATEGORY) String category);
}
The issue lies here
public class ContentRequest<T> extends RetrofitSpiceRequest<ContentResponse<T>, ContentInterface<T>>
{
String category;
public ContentRequest(Class<ContentResponse<T>> clazz, Class<ContentInterface<T>> retrofittedInterfaceClass, String category) {
super(clazz, retrofittedInterfaceClass);
this.category = category;
}
#Override
public ContentResponse<T> loadDataFromNetwork() throws Exception {
return getService().getContent(category);
}
}
In this context i know that the object returned is a Map<String, String>
contentRequest = new ContentRequest(new Class<ContentResponse<Map<String, String>>>(),
new Class<ContentInterface<Map<String, String>>>(),
"appstrings");
but i get this !!
'class()' is not public in java.lang.Class
I cant just call ContentResponse<Map<String,String>>.class, so what can I do here ?
This is not possible in Retrofit
public interface ContentInterface<T>
{
#GET("/public/content/{category}")
ContentResponse<T> getContent(#Path(PathParameters.CATEGORY) String category);
}
Retrofit uses the signature of the method at runtime to determine the return Type. In this case it would be a raw Class of ContentResponse and with a ParameterizedType of T which obviously wouldn't work.
The way to make this work is to add the ParameterizedType in the interface like so.
EDIT: Notice how the ContentInterface is no longer typed with T
public interface ContentInterface
{
#GET("/public/content/{category}")
ContentResponse<String> getContent(#Path(PathParameters.CATEGORY) String category);
}
But then that defeats the purpose because you can't have 2 method definition which has the same signature. This won't compile because of type erasure.
public interface ContentInterface
{
#GET("/public/content/{category}")
ContentResponse<String> getContent(#Path(PathParameters.CATEGORY) String category);
ContentResponse<Map<String, String> getContent(#Path(PathParameters.CATEGORY) String category);
}
So in the end you'd still need to create 2 different methods to interact with the 2 different type of data.
public interface ContentInterface
{
#GET("/public/content/{category}")
ContentResponse<String> getContentAsString(#Path(PathParameters.CATEGORY) String category);
ContentResponse<Map<String, String> getContentAsMap(#Path(PathParameters.CATEGORY) String category);
}
See #JakeWharton response here about generalization with Retrofit and why they don't really support it. I believe the same idea applies to this.

Categories