Spring doesn't correctly serialize JSON to Java Map - reactjs

I have a form that contains multiple radio inputs and one textarea input that I send using axios from a ReactJs client. The request looks like this:
axios.post("/wellbeing/" + params.wellbeingSurveyType, { formAnswersJson: formAnswers })
.then(res => {
// logic
})
.catch(err => {
// logic
})
The 'formAnswers' object looks like this:
I then receive the request from a Spring controller that looks like the following:
#PostMapping("{wellbeingSurveyType}")
public WellbeingSurveySubmission submitSurvey(
#PathVariable WellbeingSurveyType wellbeingSurveyType,
#RequestBody String formAnswersJson) throws JsonProcessingException {
var result = new ObjectMapper().readValue(formAnswersJson, HashMap.class);
return new WellbeingSurveySubmission(); //ignore this
}
When I call the 'toString()' method on the result object it seems to correctly print out the map values:
But when I try to actually operate on the object (which is parsed as a LinkedHashMap) I cannot access the keys or values:
When I try to open up the object using the debugging tool it seems to store a reference to itself as a value:
The result I want is simply a Map<String, String> that represents the JSON but I am unsure why this behavior is happening.
Any help or tips on how to do this in a better way would be greatly appreciated, thank you.

Alright the best way I found to make this work was to deconstruct the JSON object in the axios post request like so:
axios.post("/wellbeing/" + params.wellbeingSurveyType, { ...formAnswers })
.then(res => {
// logic
})
.catch(err => {
// logic
})
Works better as if I just pass the formAnswers object it unnecessarily wraps the object i.e. A hashmap that contains a single key-value pair 'formAnswers'.
Although as The Frozen One mentioned, it would be better to define a dedicated form object and take it as a param in the spring controller.

If you pass a JavaScript object as the 2nd parameter to the axios.post() function, Axios will automatically serialize the object to JSON for you.
So, with this line of code :
axios.post("/wellbeing/" + params.wellbeingSurveyType, { formAnswersJson: formAnswers })
You are basically sending object with key fromAnswersJson and value fromAnswers to your rest controller and Spring will deserilize it like a Map with key fromAnswersJson and value fromAnswers
To get what you want just send your request like this :
axios.post("/wellbeing/" + params.wellbeingSurveyType, formAnswers )

It Seems like the conversion from String to Map in java does not go smoothly from what I see in your printscreen.
Personally, I do not work like that when I handle requests. I create a dto object and give that in the controller as input. The fact that you have variables that the name is a number make that a bit more complicated since java cannot accept that as a valid variable name, but probably (did not test it) can be overcome by using #JsonProperty. So my solution would be the following
#Getter
#Setter
public class MyRequestDto {
#JsonProperty("user-comment")
private String userComment;
#JsonProperty("0")
private String zero;
#JsonProperty("1")
private String one;
#JsonProperty("2")
private String two;
...
}
I added lombok getters and setters ofcourse you can add your own if you don't use lombok.
Then replace the input in your controller
#PostMapping("{wellbeingSurveyType}")
public WellbeingSurveySubmission submitSurvey(
#PathVariable WellbeingSurveyType wellbeingSurveyType,
#RequestBody MyRequestDto request) throws JsonProcessingException {
request.getUserComment()
return new WellbeingSurveySubmission(); //ignore this
}

Related

#Cacheable in Spring does not understand dynamically assigned values

I need to dynamically assign values of cacheResolver for #Cacheable in runtime because cacheResolver has the same value for #Cacheable in every method. Hence, I use Spring AOP to dynamically assign the value but then Spring does not recognize the newly added value for cacheResolver.
Seems that AOP load #Cacheable value at the beginning.
Anyone knows how to make it work?
My AOP code:
#Aspect
#Component
#Order(1)
public class CacheableAspect {
#Pointcut("#annotation(org.springframework.cache.annotation.Cacheable)")
public void cacheablePointCut() {}
#Before("cacheablePointCut()")
public void addCacheableResolver(JoinPoint joinPoint) {
Annotation cacheableAnnotation = getCacheableAnnotation(joinPoint);
Object handler = Proxy.getInvocationHandler(cacheableAnnotation);
Field f;
try {
f = handler.getClass().getDeclaredField("memberValues");
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalStateException(e);
}
f.setAccessible(true);
Map<String, Object> memberValues;
try {
memberValues = (Map<String, Object>) f.get(handler);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
memberValues.put("cacheResolver", "cacheableResolver");
}
private Annotation getCacheableAnnotation(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
return method.getAnnotation(Cacheable.class);
}
}
My #Cacheable code in which i want cacheResolver is dynamically assigned a value:
#Cacheable(value = "test")
public int test() {
System.out.println("xxx");
return 10;
}
OK, so you are trying to dynamically change an annotation representation in the JVM during runtime. Not only is that ugly, but it probably does not work as you hope it would. It seems you found out that specific annotations are represented by a dynamic proxy instance during runtime, then you are successfully manipulating one of its field values. But annotations are meant to be immutable, aber depending on when e.g. Spring scans the annotations while wiring the application, your approach to modify the proxy fields later, while being a nice try, just comes too late.
How about a more canonical approach to use multiple cache managers and/or a resolver which dynamically does what you need to begin with? As much as I love AOP, it is not the answer to everyhing.
By the way, even though your aspect is kind of useless in this case, at least we can use it as an example of how to bind annotation values to advice methods parameters, i.e. you do not need to fetch the annotation from the method by reflection next time you write an aspect:
#Pointcut("#annotation(cacheable)")
public void cacheablePointCut(Cacheable cacheable) {}
#Before("cacheablePointCut(cacheable)")
public void addCacheableResolver(JoinPoint joinPoint, Cacheable cacheable) {
Object handler = Proxy.getInvocationHandler(cacheable);
// (...)
}

pass object parameter to method in setHeader - camel

I am new to Camel and trying to find a way to pass object to method in SetHeader.
but i am getting an error,
org.apache.camel.language.bean.RuntimeBeanExpressionException: Failed to invoke method: getCustProcessDir('${header.CUST}') on null due to: org.apache.camel.component.bean.ParameterBindingException: Error during parameter binding on method: public java.lang.String CustDao.getCustProcessDir(Cust) at parameter #0 with type: class Cust with value type: class java.lang.String and value: Cust#199b87b5
at org.apache.camel.language.bean.BeanExpression.invokeOgnlMethod(BeanExpression.java:430) ~[camel-bean-3.3.0.jar:3.3.0]
at org.apache.camel.language.bean.BeanExpression.evaluate(BeanExpression.java:164) ~[camel-bean-3.3.0.jar:3.3.0]
codes:
fromF("file:C:/Users/a/Documents/Development/input/"
+ "?recursive=false&noop=true&delay=20000&readLockLoggingLevel=WARN&shuffle=true"
+ "&readLock=idempotent&idempotentRepository=#fileRepo&readLockRemoveOnCommit=true&readLockRemoveOnRollback=true&delete=true&moveFailed=%s"
, "C:/Users/a/Documents/Development/rejected/")
.routeId("fileMoveRoute")
.process(exchange -> {
exchange.getMessage().setHeader("Application_ID", appInfo.getInstanceId());
})
.threads(appInfo.getThreadCount())
.setHeader("CUST", method(CustDao.class, "getInboundCustWithfile('${header.CamelFilePath}')"))
.setHeader("PROCESS_DIR", method(CustDao.class, "getCustProcessDir('${header.CUST}')"))
...
public String getCustProcessDir(Cust cust) {
return appInfo.getDir() + cust.getCustprofid() + "/hold/";
}
public class Cust {
private int custid;
private String custprofid;
...
}
first setHeader("CUST"..) works and i believe that Header("CUST") has returned object values.
but I am not sure how it is stored in Camel. I've tried to find them from variables window during debug but was not able to find them. too many variables to look into... Where can i find this Header values during debug?
and how can i pass object values to the method?
.setHeader("PROCESS_DIR", method(CustDao.class, "getCustProcessDir('${header.CUST}')"))
or is there a better way to pass/handle object during routing?
Thanks,
I guess the single quotes around expressions like ${header.CUST} are the problem because the RuntimeBeanExpressionException complains that it receives the String Cust#199b87b5 instead of a Cust object.
Have a look at the Camel docs for Bean binding. There are no single quotes around method parameter expressions.
About the storage of header variables: they are stored on the Message object of Camel.
Exchange -> Message -> Headers
Exchange -> ExchangeProperties

How to pass multiple Arrays from AngularJS to Spring Controller

I am trying to pass multiple list from AngularJS to Spring controller through a POST call. If I send just one list as mentioned below it works fine on the Spring controller but would like to know the best way to send multiple list from Angular to Spring.
$scope.formData = [];
var AList = [];
AList.push({'a': 1, 'b' :2});
$scope.formData = AList;
$http.post('saveData', $scope.formData).success(function(resp){});
When I try to send multiple list through the same approach but by using push, it is received in the Spring controller as shown below which I think is valid
$scope.formData.push(Alist);
$scope.formData.push(Blist);
I get something like below in Spring Controller.
[[ {a=1, b=2}, {a=3, b=4} ]]
How do I iterate this in Spring Controller and store it to my domain object.
Is this the correct approach or Is there any better ways to do it
Your frontend approach is correct. All you need to do is create an empty array, push there as many objects as you need and post it to the server.
//Create an empty array
$scope.formData = [];
//Push as many objects as you need
$scope.formData.push({'a' : 1, 'b' : 2});
$scope.formData.push({'a' : 3, 'b' : 4});
//Post it to server
$http.post('saveData', $scope.formData).success(function(resp){
//handle response
});
But your Spring side can be improved. Arrays of Objects (Object[]) are generally deprecated because they are not type safe and thus are error prone. They should be replaced with parametrized collections (from Java Collections Framework) whenever possible.
In your case you could apply following steps:
Create class of your domain object or DTO, that corresponds to received json objects.
public class MyDomainObject {
private Integer a;
private Integer b;
public MyDomainObject(){ }
//getters and setters omitted
}
Then in your endpoint method switch #RequestBody type from Object[] to List<MyDomainObject>.
#RequestMapping(path = "/saveDate", method = RequestMethod.POST)
public void postDomainObjects(#RequestBody List<MyDomainObject> myDomainObjects){
//Do some business logic.
//i.e. pass further to service or save to database via repository
}
And you receive a list of objects that are exact java representation of json objects from angular. You could iterate the list in a standard java ways, for example using foreach operator:
for (MyDomainObject obj : myDomainObjects) {
//do something with each object.
//i.e. print value of "a" field
System.out.println("Value of a: " + obj.getA());
}
or using streams in case of java 8:
//Apply some filtering, mapping or sorting to your collection.
//i.e. count sum of "a" field values.
int sumOfAFields = myDomainObjects.stream()
.mapToInt(o -> o.getA())
.sum();
NOTE:
Above solution will work if you have configured object mapper. If you use Spring boot with any of web starters you'll get it for free. In standard spring project (with configuration on your own) you must include jackson library to your project's classpath and register ObjectMapper bean in configuration class or xml.

Libgdx Load/save array in Json

I want to save an array of Mission.class which have variables as follows:
public class Mission {
public MissionEnum missionEnum;
public int progress;
public Mission(MissionEnum missionEnum, int progress) {
this.missionEnum = missionEnum;
this.progress = progress;
}
and also save missions in another java class:
public void saveMissions() {
Json json = new Json();
json.setOutputType(JsonWriter.OutputType.json);
json.addClassTag("Mission", Mission.class);
FileHandle missionFile = Gdx.files.local("missions_array.json");
missionFile.writeString(json.prettyPrint(missions), false);
}
and load missions:
public void loadMissions() {
if (Gdx.files.local("missions_array.json").exists()) {
try {
FileHandle file = Gdx.files.local("missions_array.json");
Json json = new Json();
json.addClassTag("Mission", Mission.class);
missions = json.fromJson(Array.class, Mission.class, file);
for (Mission mission : missions) {
Gdx.app.log(TAG, "Mission loaded: " + mission.missionEnum);
}
Gdx.app.log(TAG, "Load missions successful");
} catch (Exception e) {
Gdx.app.error(TAG, "Unable to read Missions: " + e.getMessage());
}
}
}
I got json like this:
[
{
"class": "Mission",
"missionEnum": "BUY_POWERUP"
},
{
"class": "Mission",
"missionEnum": "DISTANCE_ONE_RUN_2"
},
{
"class": "Mission",
"missionEnum": "BANANA_TOTAL_2",
"progress": 35
}
]
However when loadMissions() is run I got the "Load missions successful" log shown but "Mission loaded..." aren't shown without any error log. Missions appeared not loaded properly. I do not know what went wrong because another array is loaded successful the same way.
Not sure why there are no errors in the logs, since while reproducing your problem I've got an exception.
The problem is in loadMissions() method: you create new Json parser without setting the class tag:
Json json = new Json();
// add the line below
json.addClassTag("Mission", Mission.class);
missions = json.fromJson(Array.class, Mission.class, file);
....
Without the tag parser doesn't know what is "class": "Mission" in json file.
Update
Another thing that may cause this issue is the args-constructor. At least, when I added it I got an exception. If you don't use it - just delete.
Still, quite weird that you don't have any exceptions in logs'cos I definitely have.
Updated response:
Add an empty contructor and read this and this
You will either have to add a no-argument constructor to (Mission), or
you will have to add a custom serializer (see
https://code.google.com/p/libgdx/wiki/JsonParsing#Customizing_serialization)
that knows how to save a (Mission) instance and knows the appropriate
constructor to invoke when reading a (Mission) back in.
public Mission() {
// Do nothing.
}
Reading & writing JSON
The class implementing Json.Serializable must have a zero argument
constructor because object construction is done for you.
Alternatively delete the unused constructor. I think Enigo answer is also correct so I'm going to upvote his answer.
Providing Constructors for Your Classes
You don't have to provide any constructors for your class, but you
must be careful when doing this. The compiler automatically provides a
no-argument, default constructor for any class without constructors.
This default constructor will call the no-argument constructor of the
superclass. In this situation, the compiler will complain if the
superclass doesn't have a no-argument constructor so you must verify
that it does. If your class has no explicit superclass, then it has an
implicit superclass of Object, which does have a no-argument
constructor.
Note: I didn't test our responses, I have not developed games or used libgdx in the last two years.
Also read this libgdx issue: Json - constructor default value with Enum:
I don't know if this would be called a bug but, I have a case where I
have an enum like this;
Then I have a class with 2 constructors;
This second gets called by my framework, the first by Json
deserialization.
...
Previous response:
I guess that the missing progress field in some Mission classes can be the source of the issue but would be interesting to read the error logs to be sure.
I followed this, this and this to confirm this but it's hard without extra information about the exact log error.

Creating & Setting a Map into context through SpringEl

As SpringEl doc. indicates, there is el syntax for creating a list which then allows me setting it into the context as below:
List numbers = (List) parser.parseExpression("map['innermap']['newProperty']={1,2,3,4}").getValue(context);
However, I am not able to find a way of doing the same thing for Map nor I can find it in the document.
Is there a short hand way of creating a map and then setting it to context? if not, how can we go about it.
If possible a code snippet will be helpful.
Thanks in advance.
It's now possible (since 4.1, I think):
{key:value, key:value}
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html#expressions-inline-maps
No, it isn't possible yet: https://jira.spring.io/browse/SPR-9472
But you can do it with some util method, which should be registered as SpEL-function:
parser.parseExpression("#inlineMap('key1: value1, key2:' + value2)");
Where you have to parse the String arg to the Map.
UPDATE
Please, read this paragraph: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html#expressions-ref-functions.
From big height it should be like this:
public abstract class StringUtils {
public static Map<String, Object> inlineMap(String input) {
// Here is a code to parse 'input' string and build a Map
}
}
context.registerFunction("inlineMap",
StringUtils.class.getDeclaredMethod("inlineMap", new Class[] { String.class }));
parser.parseExpression("#inlineMap('key1: value1, key2:' + value2)")
.getValue(context, rootObject);

Resources