Java convert list of string to list of objects - spring-boot

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.

Related

Using annotations in spring boot for putting data in correct format

I have a field in my entity that holds phone-number. According to the conventions of the project, I need to save it in E.164 format in the DB. At the moment I use #PrePersist and #PreUpdate annotations for changing the phone number to the specified format. This method is good for one or two entities but it becomes very error-prone when you have to repeat it over and over.
I was thinking that it would be awesome if I could put the code in annotation and the annotation reads the fields and changes its value just before the persistence something like what #LastModifiedDate and annotation do. I searched the web for the codes of this annotation but I didn't understand how they managed it.
How I can write an annotation that reads the value of a field and changes it before persistence, and how I can do it before some specific operations like delete (I want to set some params before deleting the object too)
Take a look at EntityListeners.
You can create a listener that checks your custom annotation and triggers the appropriate methods.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface TheCustomAnnotation{
}
#Entity
#EntityListeners(TheListener.class)
public class TheEntity {
#TheCustomAnnotation
private String phoneNumber;
public class TheListener {
#PrePersist
public void prePersist(Object target) {
for(Field field : target.getClass().getDeclaredFields()){
Annotation[] annotations = field.getDeclaredAnnotations();
// Iterate annotations and check if yours is in it.
}
}
This is just an example.
#Pattern is a pretty powerful annotation that would be a good fit for validations if you are experienced with regular expressions.
For example,
#Pattern(regexp="^[0-9]{3}-[0-9]{3}-[0-9]{4}$")
private String phoneNumber;
The downside is that this only works for Strings though.
If you are interested more in conversions than validations, you may want to look into #JsonDeserialize if you are using Jackson.
For example:
#JsonDeserialize(using=PhoneNumberDeserializer.class)
private String phoneNumber;
Pattern phonePattern = Pattern.compile("^[0-9]{3}(.+)[0-9]{3}(.+)[0-9]{4}$");
public class PhoneNumberDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
String phone = jsonParser.getText();
if (matcher.matches(phone)) {
Matcher matcher = phonePattern.matcher(phone);
for (int i = 1; i < matcher.groupCount(); i++) {
marcher.group(i).replaceAll(".*", "");
}
}
}
}
This will work for any type, not just strings.
Sorry it's a little convoluted, I was having fun reteaching myself.

How to disable spring boot parameter split

We have many #RestController receiving phrases in common language written by users. Phrases can be very long and contains punctuation, like periods and, of course, commas.
Simplified controller example:
#RequestMapping(value = "/countphrases", method = RequestMethod.PUT)
public String countPhrases(
#RequestParam(value = "phrase", required = false) String[] phrase) {
return "" + phrase.length;
}
Spring boot default behaviour is to split parameters values at comma, so the previous controller called with this url:
[...]/countphrases?phrase=john%20and%20me,%20you%and%her
Will return "2" istead of "1" like we want. In fact with the comma split the previous call is equivalent to:
[...]/countphrases?phrase=john%20and%20me&phrase=you%and%her
We work with natural language and we need to analyze phrases exactly how the users wrote them and to know exactly how many they wrote.
We tried this solution: https://stackoverflow.com/a/42134833/1085716 after adapting it to our spring boot version (2.0.5):
#Configuration
public class MvcConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
// we hoped this code could remove the "split strings at comma"
registry.removeConvertible(String.class, Collection.class);
}
}
But it doesn't work.
Anyone know how to globally remove the "spring boot split string parameters at comma" behaviour in spring boot 2.0.5?
I find the solution.
To override a default conversion we must add a new one. If we remove the old one only it doesn't work.
The correct (example) code should be:
#Configuration
public class MvcConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.removeConvertible(String.class, String[].class);
registry.addConverter(String.class, String[].class, noCommaSplitStringToArrayConverter());
}
#Bean
public Converter<String, String[]> noCommaSplitStringToArrayConverter() {
return new Converter<String, String[]>() {
#Override
public String[] convert(String source) {
String[] arrayWithOneElement = {source};
return arrayWithOneElement;
}
};
}
}
This way any controller like the one in the main question will not split parameters values:
[...]/countphrases?phrase=a,b will return 1 (and fq=["a,b"])
[...]/countphrases?phrase=a,b&phrase=c,d will return 2 (and fq=["a,b", "c,d"])
Replacing your formatter registry with a completely new list could make you loose some needed default formatters that would come with the framework. This will also disable all String-To-Collections parsing for the entire application, on every endpoint, such that if you want to a request filter such as the following at another endpoint, it won't work:
identifiers = 12,34,45,56,67
Solution:
Just change your delimiter into something else... # or ; or $
identifiers = 12;23;34;45;56
This is what I have been doing, so I don't mess with all the goodies in the formatter registry.

GraphQl Java, How can I blindly return all variables associated with an object from query and question on handling sub classes

I'm new to GraphQL and I'm currently implementing a GraphQL API into an established Java code, using GraphQL-SPQR and I'm running into a couple issues when it comes extracting data from hierarchical classes.
The issues that I am running into are as follows.
Firstly I don't if there is an easy way to get all the data associated with a returned node. If there is, this would be most useful for my more complex classes.
Secondly when a method returns an abstract class, I only seem able to request the variables on the abstract class. I'm sure this should be possible I am just hitting my head against a wall.
As a simple example
public abstract class Animal {
private String name;
private int age;
// Constructor
#GraphQLQuery(name = "name")
public String getName() {
return name;
}
// Age getter
}
public class Dog extends Animal {
private String favouriteFood;
// Constructor
#GraphQLQuery(name = "favouriteFood")
public String getFavouriteFood() {
return favouriteFood;
}
}
public class Database {
#GraphQLQuery(name = "getanimal")
public Animal getAnimal(#GraphQLArgument(name = "animalname") String animalname) {
return database.get(name);
}
}
So in my first question what I am currently querying is.
"{animalname(name: \"Daisy\") {name age}}"
This works fine as expected. If you imagine the class however had 10 variables I would like to merely be able to write the equivalent of the following without having to look them up.
"{node(name: \"Daisy\") {ALL}}"
Is this possible?
In terms of my second question.
The follow query, throws an error ('Field 'favouriteFood' in type 'Animal' is undefined')
"{animalname(name: \"Bones\") {name age favouriteFood}}"
likewise (reading Inline Fragments of https://graphql.org/learn/queries/)
"{animalname(name: \"Bones\") {name age ... on Dog{favouriteFood}}}"
throws an error Unknown type Dog
This is annoying as I have a number of sub classes which could be returned and may require handling in different fashions. I think I can understand why this is occuring as GraphQL has no knowledge as to what the true class is, only the super class I have returned. However I'm wondering if there is a way to fix this.
Ultimately while I can get past both these issues by simply serialising all the data to JSON and sending it back, it kind of gets rid of the point of GraphQL and I would rather find an alternate solution.
Thank you for any response.
Apologies if these are basic questions.
Answering my own question to help anyone else who has this issue.
The abstract class needs to have #GraphQLInterface included, as shown below
#GraphQLInterface(name = "Animal ", implementationAutoDiscovery = true)
public abstract class Animal {
private String name;
private int age;
// Constructor
#GraphQLQuery(name = "name")
public String getName() {
return name;
}
// Age getter
}
The following code was found after much solution and was created by the creator of SPQR. Effectively, when setting up your schema you need to declare an interface mapping strategy. The code below can be copied wholesale with only the "nodeQuery" variable being replaced with the service you are using to containing your "#GraphQLQuery" and "#GraphQLMutation" methods.
final GraphQLSchema schema = new GraphQLSchemaGenerator()
.withInterfaceMappingStrategy(new InterfaceMappingStrategy() {
#Override
public boolean supports(final AnnotatedType interfase) {
return interfase.isAnnotationPresent(GraphQLInterface.class);
}
#Override
public Collection<AnnotatedType> getInterfaces(final AnnotatedType type) {
Class clazz = ClassUtils.getRawType(type.getType());
final Set<AnnotatedType> interfaces = new HashSet<>();
do {
final AnnotatedType currentType = GenericTypeReflector.getExactSuperType(type, clazz);
if (supports(currentType)) {
interfaces.add(currentType);
}
Arrays.stream(clazz.getInterfaces())
.map(inter -> GenericTypeReflector.getExactSuperType(type, inter))
.filter(this::supports).forEach(interfaces::add);
} while ((clazz = clazz.getSuperclass()) != Object.class && clazz != null);
return interfaces;
}
}).withOperationsFromSingleton(nodeQuery)// register the service
.generate(); // done ;)
graphQL = new GraphQL.Builder(schema).build();
As this solution took some hunting, I'm going to start a blog soon with the other solutions I've stumbled on.
With regards to having a query that just returns all results. This is not possible in GraphQL. One workaround I might write is to have a endpoint that returns JSON of the entire object and the name of the object, then I can just use ObjectMapper to convert it back.
I hope this helps other people. I'm still looking into an answer for my first question and will update this post when I find one.

#RestController removes spaces in string

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.

From request object to the database

I have an app with an AngularJS front-end and a Spring MVC back-end. I'm having some trouble with converting/mapping request objects to domain/dto objects.
On one page you can add a new order to the system, the POST payload would look something like this:
{
memo: "This is some extra info for order",
orderLines: [{productId:3, quantity:4}, {productId:2, quantity:5}, {productId:1, quantity:4}],
shippingDate: "2014-10-08T19:16:19.947Z",
warehouseId: 2
}
The Spring MVC controller method looks like this:
#RequestMapping(value = "/order", method = RequestMethod.POST)
public ResponseEntity<Void> addOrder(#RequestBody #Valid OrderRequest orderRequest, UriComponentsBuilder b) throws Exception {
// the magic
}
Where OrderRequest is filled with the values of the POST request, the OrderRequest and OrderLineRequest look like this:
public class OrderRequest {
private Long id;
private Date shippingDate;
private String memo;
private List<OrderLineRequest> orderLines;
private Long warehouseId;
public OrderRequest() {
}
// getters and setters ommitted
}
public class OrderLineRequest {
private Long id;
private String productCode;
private int quantity;
public OrderLineRequest() {
}
}
My question now is, in order to save an Order object with orderService.add(order) I need to construct the Order object based on the values that were sent in the request. Where/how do I do this?
OPTION 1
The OrderRequest class could have a makeOrder() method with just returns an Order object like so:
public Order makeOrder() {
Order order = new Order();
order.setMemo(this.memo);
order.setShippingDate(this.shippingDate);
...
}
Then I'd have to map the OrderLineRequest which could have their own makeOrderLine method:
public OrderLine makeOrderLine() {
OrderLine orderLine = new OrderLine();
orderLine.setQuantity = this.quantity;
...what to do with only the productId?
}
As you can see I can set the quantity but in the request I only received the productId, but in the database I save the productCode, productName as well, so I need that info from the database, but I don't want to make a database call from the Request object...I also don't want to half of the mapping in the request object and the rest of the mapping in the controller where I do have access to the services.
OPTION 2
I can use Dozer to do the mapping for me, but that would mean injecting the services into the Dozer custom converters which seem equally unclean to me...
OPTION 3
I pass the OrderRequest object to the service layer and let the service layer handle it, but my question would remain, how exactly would the service layer convert it, say you have the method addOrder like this:
public void addOrder(OrderRequest orderRequest) {
}
Would you call another service to convert from one to the other as I don't really want this conversion in a business logic method?
Any help would be appreciated
use the #RequestBody to map your jsonObject that is send with the request , to a DTO .
please refer to the following tutorial .
hope that helps .
and please ask if there is something not clear .

Resources