Spring Data REST select fields at runtime - java

I'm using Spring Boot, Spring Data REST, Spring HATEOAS in my project.
My domain model is quite complex and I'd like to follow some of REST best practice as the fields selection.
I do know Spring projections but I'm looking for a way to tell what fields I need from the client at runtime. Simple as call GET /cars?fields=manufacturer,model,id,color.
Because I'd like to take advantage of Spring Data, I think I should create something in between the REST call and Spring.
Do you know some good resource/example to create something like that?

With Squiggly Filter
#GetMapping(value="cars")
public #ResponseBody List<Car> getCars(#RequestParam("fields") String fields){
List<Car> carList ;
-------
ObjectMapper mapper = = Squiggly.init(new ObjectMapper(), fields);
System.out.println(SquigglyUtils.stringify(mapper, carList));
-------
}
More on,
https://github.com/bohnman/squiggly-java
With SimpleBeanPropertyFilter,
Car class,
#JsonFilter("myfilter")
public class Car {
public String color;
public String model;
public String type;
At controller,
#GetMapping(value = "/cars")
public ResponseEntity<?> getCars(#RequestParam("fields") String fields) throws IOException {
List<Car> list = Arrays.asList(new Car("pink", "verna", "sedan"), new Car("black", "i10", "hatchback"),
new Car("voilet", "brizza", "SUV"));
SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("myfilter",
SimpleBeanPropertyFilter.filterOutAllExcept(fields.split(",")));
ObjectMapper mapper = new ObjectMapper().setFilterProvider(filterProvider);;
return new ResponseEntity<>(mapper.readValue(mapper.writeValueAsString(list),Object.class), HttpStatus.OK);
}

Related

Convert object to string Spring Boot rest API

I'm receiving an id (integer) and a executor (String) in my controller (Rest API). However, when looking at my database, I see that the string is being inserted into the database as an object. Example of database entry:
{
   "executor": "Pietje"
}
Controller:
#PostMapping(path = "/accept/{id}")
public String acceptAssignment(#Valid #PathVariable Integer id, #RequestBody String executor) {
return assignmentService.acceptAssignment(id, executor);
}
Service implementation:
#Override
public String acceptAssignment(Integer id, String executor) {
Assignment assignment = assignmentRespository.findById(id).orElse(null);
assignment.setExecutor(String.valueOf(executor));
AssignmentDTO assignmentDTO = assignmentConverter.convertEntityToDto(assignment);
assignmentRespository.save(assignment);
return assignmentDTO.getExecutor();
}
What am I doing wrong, and how can I fix it?
I could pass along the entire DTO instead of just the 'executor' value, but that doesn't seem efficient. As far as I know, the problem is not with the frontend but I could add the React code if necessary.
TL;DR - you're using a String containing a JSON-object as if it was an attribute of this JSON-object. The solution it to treat this JSON properly.
Note that you don't need to mess with desirialization manually, let the JSON-converter of the framework do its job.
All that you need is a simple POJO:
#Getter
#Setter
public class AssignmentExecutor {
private String executor;
}
The above POJO can be automatically translated in & to the following of JSON without any effort from your side (owing to the magic of Spring):
{
"executor": "Pietje"
}
It would be automatically parsed to the proper type by a Spring's message-converter, you only need to specify that you need an AssignmentExecutor instead of a plain String.
#PostMapping(path = "/accept/{id}")
public String acceptAssignment(#Valid #PathVariable Integer id,
#RequestBody AssignmentExecutor executor) {
return assignmentService.acceptAssignment(id, executor);
}
Note
Introducing this new type would not require any changes in the Assignment, executor can still be represented as a String field.
By invoking orElse(null) on the optional result, you're creating a potential problem by depriving the possibility to get a meaningful exception if the data that corresponds to the given id was not found. In such a case, your current code would trigger a NullPointerException right on the next line. Instead, I would advise providing a suitable exception via Optional.orElseThrow().
A now again all that you need is to return an instance of AssignmentExecutor and it would be automatically converted into JSON:
#Override
public String acceptAssignment(Integer id, AssignmentExecutor executor) {
Assignment assignment = assignmentRespository.findById(id)
.orElseThrow(() -> new MyException("Assignment with id " + id + " was not found"));
assignment.setExecutor(executor.getExecutor());
assignmentRespository.save(assignment);
return executor;
}
You can use ObjectMapper to fetch the desired key-value from the #RequestBody String executor as:
Approach Here:
The #RequestBodythat you are getting from the API is in the object form and using ObjectMapper , it will be converted into a Map of attributes as in the API request and we can fetch the desired key-value pair.
ObjectMapper mapper = new ObjectMapper();
Map<String,Object> map = mapper.readValue(executor, new TypeReference<>() {});
String s = (String) map.get("executor");
Added in acceptAssignment method as:
#Override
public String acceptAssignment(Integer id, String executor) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Map<String,Object> requestMap = mapper.readValue(executor, new TypeReference<>() {});
Assignment assignment = assignmentRespository.findById(id).orElse(null);
assignment.setExecutor((String)requestMap.get("executor"));
AssignmentDTO assignmentDTO = assignmentConverter.convertEntityToDto(assignment);
assignmentRespository.save(assignment);
return assignmentDTO.getExecutor();
}

Best way to explicitly convert POJO into JSON in Spring RestController

I have an existing REST API in a spring boot project that looks somewhat like this:
#GetMapping(value = "/api/projects")
public List<Project> getProjects() {
List<Project> projects = projectsRepository.findAll();
List<Project> processed = processProjects(projects);
return processed;
}
When this method is called the returned JSON response looks something like this:
{
"JSON":"[
{
"id":"aaa",
"simple":"SYMBOLIC_VALUE_BBB",
"nested1":{
"field1":"SYMBOLIC_VALUE_C1",
"field2":"nonSymbolicValueC2",
"field3":"SYMBOLIC_VALUE_C3"
},
"nested2":{
"fieldA":"SYMBOLIC_VALUE_DDD"
}
},
...
]",
"mode":"application/json"
}
The symbolic values are being translated into a human readable form in the frontend. Everything works fine. But now I also need a second version of this method that does the translation on the backend side. Something like this:
#GetMapping(value = "/api/v2/projects")
public String getProjects() {
List<Project> projects = projectsRepository.findAll();
String projectsAsJson = ???
String processedJson = processProjectsJson(projectsAsJson);
return processedJson;
}
What would I put where the three Question Marks (???) are? I want to use the same json serialization that is used automagically by the Spring Framework. It should be robust against any configuration changes that may happen in the future.
Thank you very much.
Add a attribute ObjectMapper in your Controller, use dependency injection to get it, and then use : mapper.writeValueAsString(myObject);
Something like that:
#Autowired
private ObjectMapper mapper;
#GetMapping(value = "/api/v2/projects")
public String getProjects() {
List<Project> projects = projectsRepository.findAll();
String projectsAsJson = mapper.writeValueAsString(projects);
String processedJson = processProjectsJson(projectsAsJson);
return processedJson;
}
Let me know if it is not working.

Spring Boot using Json as request parameters instead of an entity/model

Our company is planning to switch our microservice technology to Spring Boot. As an initiative I did some advanced reading and noting down its potential impact and syntax equivalents. I also started porting the smallest service we had as a side project.
One issue that blocked my progress was trying to convert our Json request/response exchange to Spring Boot.
Here's an example of the code: (This is Nutz framework for those who don't recognize this)
#POST
#At // These two lines are equivalent to #PostMapping("/create")
#AdaptBy(type=JsonAdapter.class)
public Object create(#Param("param_1") String param1, #Param("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
On PostMan or any other REST client I simply pass POST:
{
"param_1" : "test",
"param_2" : 1
}
I got as far as doing this in Spring Boot:
#PostMapping("/create")
public Object create(#RequestParam("param_1") String param1, #RequestParam("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
I am not sure how to do something similar as JsonAdapter here. Spring doesn't recognize the data I passed.
I tried this but based on the examples it expects the Json paramters to be of an Entity's form.
#RequestMapping(path="/wallet", consumes="application/json", produces="application/json")
But I only got it to work if I do something like this:
public Object (#RequestBody MyModel1 model1) {}
My issue with this is that MyModel1 may not necessarily contain the fields/parameters that my json data has.
The very useful thing about Nutz is that if I removed JsonAdapter it behaves like a regular form request endpoint in spring.
I couldn't find an answer here in Stack or if possible I'm calling it differently than what existing spring devs call it.
Our bosses expect us (unrealistically) to implement these changes without forcing front-end developers to adjust to these changes. (Autonomy and all that jazz). If this is unavoidable what would be the sensible explanation for this?
In that case you can use Map class to read input json, like
#PostMapping("/create")
public Object create(#RequestBody Map<String, ?> input) {
sout(input.get("param1")) // cast to String, int, ..
}
I actually figured out a more straightforward solution.
Apparently this works:
#PostMapping("/endpoint")
public Object endpoint(#RequestBody MyWebRequestObject request) {
String value1 = request.getValue_1();
String value2 = request.getValue_2();
}
The json payload is this:
{
"value_1" : "hello",
"value_2" : "world"
}
This works if MyRequestObject is mapped like the json request object like so. Example:
public class MyWebRequestObject {
String value_1;
String value_2
}
Unmapped values are ignored. Spring is smart like that.
I know this is right back where I started but since we introduced a service layer for the rest control to interact with, it made sense to create our own request model object (DTOs) that is separate from the persistence model.
You can use #RequestBody Map as a parameter for #PostMapping, #PutMapping and #PatchMapping. For #GetMapping and #DeleteMapping, you can write a class which implements Converter to convert from json-formed request parameters to Map. And you would register that class as a bean with #Component annotation. Then you can bind your parameters to #RequestParameter Map.
Here is an example of Converter below.
#Component
public class StringToMapConverter implements Converter<String, Map<String, Object>> {
private final ObjectMapper objectMapper;
#Autowired
public StringToMapConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public Map<String, Object> convert(String source) {
try {
return objectMapper.readValue(source, new TypeReference<Map<String, Object>>(){});
} catch (IOException e) {
return new HashMap<>();
}
}
}
If you want to exclude specific field of your MyModel1 class, use #JsonIgnore annotation onto the field like below.
class MyModel1 {
private field1;
#JsonIgnore field2;
}
Then, I guess you can just use what you have done.(I'm not sure.)
public Object (#RequestBody MyModel1 model1) {}
i think that you can use a strategy that involve dto
https://auth0.com/blog/automatically-mapping-dto-to-entity-on-spring-boot-apis/
you send a json to your rest api that is map like a dto object, after you can map like an entity or use it for your needs
try this:
Add new annotation JsonParam and implement HandlerMethodArgumentResolver of this, Parse json to map and get data in HandlerMethodArgumentResolver
{
"aaabbcc": "aaa"
}
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonParam {
String value();
}
#Component
public class JsonParamMethodResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonParam.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
RepeatedlyRequestWrapper nativeRequest = webRequest.getNativeRequest(RepeatedlyRequestWrapper.class);
if (nativeRequest == null) {
return null;
}
Gson gson = new Gson();
Map<String, Object> response = gson.fromJson(nativeRequest.getReader(), new TypeToken<Map<String, Object>>() {
}.getType());
if (response == null) {
return null;
}
JsonParam parameterAnnotation = parameter.getParameterAnnotation(JsonParam.class);
String value = parameterAnnotation.value();
Class<?> parameterType = parameter.getParameterType();
return response.get(value);
}
}
#Configuration
public class JsonParamConfig extends WebMvcConfigurerAdapter {
#Autowired
JsonParamMethodResolver jsonParamMethodResolver;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(jsonParamMethodResolver);
}
}
#PostMapping("/methodName")
public void methodName(#JsonParam("aaabbcc") String ddeeff) {
System.out.println(username);
}

JSON parameter in spring MVC controller

I have
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
SessionInfo register(UserProfile profileJson){
...
}
I pass profileJson this way:
http://server/url?profileJson={"email": "mymail#gmail.com"}
but my profileJson object has all null fields. What should I do to make spring parse my json?
The solution to this is so easy and simple it will practically make you laugh, but before I even get to it, let me first emphasize that no self-respecting Java developer would ever, and I mean EVER work with JSON without utilizing the Jackson high-performance JSON library.
Jackson is not only a work horse and a defacto JSON library for Java developers, but it also provides a whole suite of API calls that makes JSON integration with Java a piece of cake (you can download Jackson at http://jackson.codehaus.org/).
Now for the answer. Assuming that you have a UserProfile pojo that looks something like this:
public class UserProfile {
private String email;
// etc...
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
// more getters and setters...
}
...then your Spring MVC method to convert a GET parameter name "profileJson" with JSON value of {"email": "mymail#gmail.com"} would look like this in your controller:
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper; // this is your lifesaver right here
//.. your controller class, blah blah blah
#RequestMapping(value="/register", method = RequestMethod.GET)
public SessionInfo register(#RequestParam("profileJson") String profileJson)
throws JsonMappingException, JsonParseException, IOException {
// now simply convert your JSON string into your UserProfile POJO
// using Jackson's ObjectMapper.readValue() method, whose first
// parameter your JSON parameter as String, and the second
// parameter is the POJO class.
UserProfile profile =
new ObjectMapper().readValue(profileJson, UserProfile.class);
System.out.println(profile.getEmail());
// rest of your code goes here.
}
Bam! You're done. I would encourage you to look through the bulk of Jackson API because, as I said, it is a lifesaver. For example, are you returning JSON from your controller at all? If so, all you need to do is include JSON in your lib, and return your POJO and Jackson will AUTOMATICALLY convert it into JSON. You can't get much easier than that. Cheers! :-)
This could be done with a custom editor, that converts the JSON into a UserProfile object:
public class UserProfileEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
ObjectMapper mapper = new ObjectMapper();
UserProfile value = null;
try {
value = new UserProfile();
JsonNode root = mapper.readTree(text);
value.setEmail(root.path("email").asText());
} catch (IOException e) {
// handle error
}
setValue(value);
}
}
This is for registering the editor in the controller class:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(UserProfile.class, new UserProfileEditor());
}
And this is how to use the editor, to unmarshall the JSONP parameter:
#RequestMapping(value = "/jsonp", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
#ResponseBody
SessionInfo register(#RequestParam("profileJson") UserProfile profileJson){
...
}
You can create your own Converter and let Spring use it automatically where appropriate:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
#Component
class JsonToUserProfileConverter implements Converter<String, UserProfile> {
private final ObjectMapper jsonMapper = new ObjectMapper();
public UserProfile convert(String source) {
return jsonMapper.readValue(source, UserProfile.class);
}
}
As you can see in the following controller method nothing special is needed:
#GetMapping
#ResponseBody
public SessionInfo register(#RequestParam UserProfile userProfile) {
...
}
Spring picks up the converter automatically if you're using component scanning and annotate the converter class with #Component.
Learn more about Spring Converter and type conversions in Spring MVC.
This does solve my immediate issue, but I'm still curious as to how you might pass in multiple JSON objects via an AJAX call.
The best way to do this is to have a wrapper object that contains the two (or multiple) objects you want to pass. You then construct your JSON object as an array of the two objects i.e.
[
{
"name" : "object1",
"prop1" : "foo",
"prop2" : "bar"
},
{
"name" : "object2",
"prop1" : "hello",
"prop2" : "world"
}
]
Then in your controller method you recieve the request body as a single object and extract the two contained objects. i.e:
#RequestMapping(value="/handlePost", method = RequestMethod.POST, consumes = { "application/json" })
public void doPost(#RequestBody WrapperObject wrapperObj) {
Object obj1 = wrapperObj.getObj1;
Object obj2 = wrapperObj.getObj2;
//Do what you want with the objects...
}
The wrapper object would look something like...
public class WrapperObject {
private Object obj1;
private Object obj2;
public Object getObj1() {
return obj1;
}
public void setObj1(Object obj1) {
this.obj1 = obj1;
}
public Object getObj2() {
return obj2;
}
public void setObj2(Object obj2) {
this.obj2 = obj2;
}
}
Just add #RequestBody annotation before this param

How to create a Backbone model from a POJO or a JavaBean?

I have a POJO named Person.java, is there any bash or utility that allows me to create a Backbone model named person.js from Person.java so I don't have to re-type all the fields again?
Thank you.
If you're using the Jackson JSON Processor http://jackson.codehaus.org/ to translate your POJO model code to JSON, you should not have to recreate any of the properties on your Backbone model. A simple example:
public String getPerson(){
Person personPOJOInstance = new Person();
ObjectMapper mapper = new ObjectMapper();
StringWriter sw = new StringWriter();
try{
mapper.writeValue(sw, personPOJOInstance);
pojoJSON = sw.getBuffer().toString();
}
catch(IOException exc){
}
return pojoJSON;
}
You don't even have to worry about doing this if you're using a Spring MVC controller and mark your controller method with the following #RequestMapping annotation, like so:
#RequestMapping(method= RequestMethod.GET, produces = "application/json", value="/path/to/controller/method")
public #ResponseBody getPerson(){
return new Person();
}
Finally, your backbone model is as simple as:
var Person = Backbone.Model.extend({
url: '/path/to/controller/method'
});
You're not required to specify any default attributes on your Backbone model, although it may be a good idea to do so.
Now when you fetch the model, you can access any of the properties that came from the original POJO on the Backbone model like this:
//instantiate and fetch your model.
var person = new Person();
person.fetch();
...
//access properties on your model.
var name = person.get('name');
There is a promising project in github, that promises to do exactly that:
https://github.com/juhasipo/JMobster

Categories

Resources