I have found an interesting bug/feature while writing webservice. I am returning JSON data for selection filter in frontend. Then these selections are returned me back to get data. I am sending it to a database so I need exactly the same format.
Problem is when there are more then two spaces in its name. On JSON output it removes any number of spaces and leaves only one. But I need all of them. How can I force RestController to leave there all spaces?
#RestController
#RequestMapping("/")
public class FilterController {
private static final Logger log = Logger.getLogger(FilterController.class);
#Autowired
SentimentService sentimentService;
#RequestMapping(value="/filter", method=RequestMethod.GET)
public Filter getValues(#RequestParam(value="sources", defaultValue="50") int sourceNb) {
Filter filter = sentimentService.filterGetValue();
return filter;
}
}
This is my controller. Filter is object with tree structure. One of them is Product layer. I even added sysout there. The spaces are saved in the object but not passed to JSON output.
public class Product {
private String name;
public String getName() {
System.out.println("Name: " + name); // it really has two spaces there
return name;
}
public void setName(String value) {
this.name = value;
}
}
Is there any annotation I need to add to my class variable to be left as it is? I couldn't find anything useful so I just hope that it can be done easily. Thanks.
Related
I have two properties in my class. I changed the names, but one of the properties ends with two uppercase letters and the other does not.
private final List<String> variableAB = new ArrayList<>();
private final List<String> variableWord = new ArrayList<>();
and they both have the same getter setters:
public void setVariableAB(String recipients)
{
variableAB.clear();
if (StringUtils.isNotBlank(recipients)){
variableAB.addAll(Arrays.asList(recipients.split(PARAM_SEPARATOR)));
}
}
public List<String> getVariableAB()
{
return Collections.unmodifiableList(this.variableAB);
}
public void setVariableWord(String recipients)
{
variableWord.clear();
if (StringUtils.isNotBlank(recipients)){
variableWord.addAll(Arrays.asList(recipients.split(PARAM_SEPARATOR)));
}
}
public List<String> getVariableWord()
{
return Collections.unmodifiableList(this.variableWord);
}
When I try to set the property variableAB with
PropertyUtils.setSimpleProperty()
I get the error in the title, while debugging it turns out the PropertyUtil can recognize the read method, but does not find the write method.
I thought it was a problem with the name of the varibale so I changed it to not end with two uppercase letters but it did not help. My second guess was a type mismatch in the setter parameters; however setting variableWord does not produce this error.
In order to increase performance, my API is going to receive an array of string that I need to convert to an array of objects.
My array looks like this:
List<String> listPersons = ["1, Franck, 1980-01-01T00:00:00, 00.00", "2, Martin, 1989-01-01T00:00:00, 00.00"];
How could I easily convert it to a list of Persons (List), if possible using Java 8 so I don't have to create a loop and manually explode the String?
class Person {
private Integer id;
private String name;
private Date dateOfBirth;
// getter and setter
}
Ideally I'd like to automate this directly using SpringBoot - Using a custom converter such as:
public class StringToPersonConverter implements Converter<String, Person> {
#Override
public Person convert(String from) {
String[] data = from.split(",");
return new Person(Integer.parseInt(data[0]), data[1], new Date(data[2]));
}
}
Declaring the converter:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToCreditCardConverter());
}
}
And ideally map it from my controller directly?
#RequestMapping(value = "/insertPersons", method = RequestMethod.POST)
#ResponseBody
public String savePersons(#RequestBody List<Person> listPersons) {}
Unfortunately, it doesn't seem to detect my converter and it's throwing an error ;/
Any idea? Thanks
As a side note : String[] data = from.split(","); will not work with , as character separator because values use this character, for example : 1980-01-01T00:00:00, 00.00.
In order to increase performance, my API is going to receive an array
of string that I need to convert to an array of objects.
Here is a non answer. JSON is a format that consumes very few memory (literally characters) to convey the structure. So you don't need and even don't have to concatenate in a single String distinct information that here represent difference Person.
Designing an API that waits for unstructured JSON/text and restructuring the JSON/text in the backend in an anti pattern : it is not more efficient, it makes the API unclear and add boiler plate code both in the front and the back end.
I would like to have a single service that can respond with different levels of information:
Level 1:
{
"field_1": "value_1",
"field_2": "value_2"
}
Level 2:
{
"field_1": "value_1",
"field_2": "value_2",
"field_3": "value_3"
}
Level 3:
{
"field_1": "value_1",
"field_2": "value_2",
"field_3": "value_3",
"field_4": "value_4"
}
My first approach is using a parameter in the request such like this:
#RestController
public <ResponseObject> getInfo(..., #RequestParam levelInfo) {
service.getInfo(..., levelInfo);
}
#Service
public <ResponseObject> getInfo(..., levelInfo) {
if (levelInfo == 1)
return setupResponseLevel1();
if (levelInfo == 2)
return setupResponseLevel1();
if (levelInfo == 3)
return setupResponseLevel1();
}
private <ResponseObject> setupResponseLevel1() {
responseObject.setField_1(repository.getField1());
responseObject.setField_2(repository.getField2());
return responseObject;
}
private <ResponseObject> setupResponseLevel2() {
responseObject = this.setupResponseLevel1();
responseObject.setField_3(repository.getField3());
return responseObject;
}
private <ResponseObject> setupResponseLevel3() {
responseObject = this.setupResponseLevel2();
responseObject.setField_4(repository.getField4());
return responseObject;
}
#JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class ResponseObject {
private String field_1;
private String field_2;
private String field_3;
private String field_4;
// Getters & setters...
}
My API will be very large and I need to find a pattern that I can reuse in many services.
Do you know any cleaner way to do it?
EDIT: I'm sorry, I did not explain with the properly precision.
I like the ideas of the answers but I have added more code to the #Service so that you understand that the problem is not only the presentation of the response (JSON) but also the saving of the cost of obtaining the information (queries to BBDD).
You could use #JsonView annotation for that. A simple example would look like this
public class Views {
public static class LevelOne {
}
public static class LevelTwo extends LevelOne {
}
}
public class ResponseObject {
#JsonView(Views.LevelOne.class)
private String field_1;
#JsonView(Views.LevelOne.class)
private String field_2;
#JsonView(Views.LevelTwo.class)
private String field_3;
}
and method for serializing
public String toJson(Class<?> view) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writerWithView(view).writeValueAsString(this);
}
if the toJson method is called with Views.LevelOne.class as an argument, only the field_1 and field_2 will be serialized. If it is called with Views.LevelTwo.class, all three fields would be serialized.
You can choose which view to use based on parameter as you suggested.
What about a URL scheme along the lines of:
https://your.api/info/2
then
#RequestMapping("/info/{levelInfo}")
public <ResponseObject> getInfo(..., #PathVariable String levelInfo) {
where 2 is the level. As the level is part of your domain, i.e. it has relevance to your clients, you could capture that in the database with a level column for each field, 1,2,3,4 etc then use the repository to:
repository.findByLevelLessThanEqual(levelInfo)
which could return a package of information containing all the required fields. So if you ask for level 1, you only get level 1 fields. If you ask for level 4, you get all fields up to and including level 4
Spring JPA LessThanEqual documentation
Consider a situation where we can have several mappings with the same regular expression, which should be validated programmatically (for instance against database).
(this is not a valid piece of code, I am trying just to explain what I am trying to achieve. Note the regular expressions in the url path)
// Animal controller
#GetMapping(path = "/{animal-category [a-z-]+}/{animal-name [a-z-]+}")
public void show(#PathVariable String animalCategory, #PathVariable String animalName) {
// if animalCategory is not found in database, continue with next controller
}
// Plants controller
#GetMapping(path = "/{plant-category [a-z-]+}/{plant-name [a-z-]+}")
public void show(#PathVariable String plantCategory, #PathVariable String plantName) {
// if plantCateogry is not found in database, continue with next controller - as there is no more, it should return 404
}
You can achieve this problem with a general controller method like this:
// General controller method
#GetMapping(path = "/{category [a-z-]+}/{name [a-z-]+}")
public void show(#PathVariable String category, #PathVariable String name) {
// look in database for the category
if(isAnimalCatagory) {
return showAnimal(category, name);
}
else if(isPlantCategory) }
return showPlant(category, name);
}
return "redirect:/404";
}
public void showAnimal(String animalCategory, String animalName) {
// for animal categories
}
public void showPlant(String plantCategory, String plantName) {
// for plant categories
}
The client periodically calls an async method (long polling), passing it a value of a stock symbol, which the server uses to query the database and return the object back to the client.
I am using Spring's DeferredResult class, however I'm not familiar with how it works. Notice how I am using the symbol property (sent from client) to query the database for new data (see below).
Perhaps there is a better approach for long polling with Spring?
How do I pass the symbol property from the method deferredResult() to processQueues()?
private final Queue<DeferredResult<String>> responseBodyQueue = new ConcurrentLinkedQueue<>();
#RequestMapping("/poll/{symbol}")
public #ResponseBody DeferredResult<String> deferredResult(#PathVariable("symbol") String symbol) {
DeferredResult<String> result = new DeferredResult<String>();
this.responseBodyQueue.add(result);
return result;
}
#Scheduled(fixedRate=2000)
public void processQueues() {
for (DeferredResult<String> result : this.responseBodyQueue) {
Quote quote = jpaStockQuoteRepository.findStock(symbol);
result.setResult(quote);
this.responseBodyQueue.remove(result);
}
}
DeferredResult in Spring 4.1.7:
Subclasses can extend this class to easily associate additional data or behavior with the DeferredResult. For example, one might want to associate the user used to create the DeferredResult by extending the class and adding an additional property for the user. In this way, the user could easily be accessed later without the need to use a data structure to do the mapping.
You can extend DeferredResult and save the symbol parameter as a class field.
static class DeferredQuote extends DeferredResult<Quote> {
private final String symbol;
public DeferredQuote(String symbol) {
this.symbol = symbol;
}
}
#RequestMapping("/poll/{symbol}")
public #ResponseBody DeferredQuote deferredResult(#PathVariable("symbol") String symbol) {
DeferredQuote result = new DeferredQuote(symbol);
responseBodyQueue.add(result);
return result;
}
#Scheduled(fixedRate = 2000)
public void processQueues() {
for (DeferredQuote result : responseBodyQueue) {
Quote quote = jpaStockQuoteRepository.findStock(result.symbol);
result.setResult(quote);
responseBodyQueue.remove(result);
}
}