I'm serializing a form and returning JSON like so
var dataForm = JSON.stringify($('#form').serializeObject());
Then in my action class I create a JSON model object using gson.fromJson()
SomeObj so = gson.fromJson(dataForm, SomeObj.class);
In SomeObj I have a String array field
public class SomeObj {
private String[] someField;
public String[] getSomeField() {
return this.someField;
}
public void setSomeField(String[] someField) {
this.someField = someField;
}
}
I have several checkboxes with the name someField. When multiple checkboxes are selected there will be a json array created and correctly mapped on to the model class
So for example
{"someField":["someValue1", "someValue2"]}
The problem is when there is only one checkbox selected the json will look like this
{"someField":"someValue1"}
This will generate the below exception, because it's expecting an array instead of a String
Caused by: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING
Any ideas how to fix this? someField is not the only value I'm returning. There are also Booleans and Strings that I omitted
I fixed this by adding a hidden field with an empty value. It's not a very elegant solution and should be a better way but it works
<input type="hidden" name="someField" value="">
Related
I'm a newbie in java spring. I have checked for solutions online and I can't seem to get my hands on anything helpful.
I have a form that has been bound to an Entity, however, I have a field in that form that was not bound, which I receive as a requestParam.
So when the field is submitted I need to validate that parameter to ensure it's not empty.
#PostMapping("/store")
public String store(#Valid Quote quote,BindingResult result, Model model,#RequestParam(required=false) String [] tagsFrom) {
if(result.hasErrors()) {
model.addAttribute("jsontags", returnTagsAsJson(tagRepo.findAll()));
return "admin/quotes/create";
}
List<Tag> listtags = new ArrayList<Tag> ();
for(String tag : tagsFrom) {
Tag theTag = new Tag();
theTag.setTag(tag);
theTag.setCreatedAt(new Date());
theTag.setUpdatedAt(new Date());
if(tagRepo.findByTag(tag) == null) {
tagRepo.save(theTag);
}
listtags.add(tagRepo.findByTag(tag));
}
quote.setTags(listtags);
quoteRepo.save(quote);
return "redirect:/dashboard/quotes";
}
What I have tried;
I created a custom validation and added the annotation to the parameter but that gave an error "The annotation #EmptyArrayString is disallowed for this location"
public String store(#Valid Quote quote,BindingResult result, Model model,
#RequestParam(required=false) #EmptyArrayString String [] tagsFrom)
I have tried using #NotEmpty on the parameter which throws NullPointerException
I need a solution that allows me to display the error on the HTML form like this
<span th:if="${#fields.hasErrors('tags')}"
th:errors="${quote.tags}" class="errors">
</span>
So when the field is submitted I need to validate that parameter to ensure it's not empty.
,#RequestParam(required=false) String [] tagsFrom
By default, required is set to true. So, if the URL must have the param, you shouldn't do required=false.
String [] tagsFrom implies you expect a bunch of tag params. But, is it of the form http://localhost:xxx?param=1,2,3 or
http://localhost:xxx?param1=1¶m2="stringvalue" ?
For the first one, you can have the mapping method as:
public String store(...#RequestParam List<String> param)
For the second one, you can do:
public String store(...#RequestParam Map<String,String> allQueryParameters)
You can then do your necessary validations.
Read more here
Heyy,
I want to take a list of data in my request param,here "personIdCollection" is a set of list but when i am hitting through postman i am getting a bad request.
Here is my code.
controller
#PostMapping("/face-tag-data")
public String getFaceTaggedData(#RequestParam String projectId,#RequestParam List<String> personIdCollection) {
return null;
}
and here is my ajax
var data = {};
data.personIdCollection = personIdCollection;
data.projectId = $("#projectId").val();
$.ajax({
type:'POST',
url:contextPath+'/face-tag-data',
data:data,
success:function(resp){
console.log(resp);
},
failure:function(resp){
console.log(resp);
}
});
This is working for me. I do not use an ajax-request but instead submit my form directly but it should work either way.
My controller looks like:
#RequestMapping(value="addSingleArticles", method=RequestMethod.POST)
public #ResponseBody String addSingleArticles(
ArticleSubmitData pupilPackageData,
HttpServletRequest request) {
... // do something with the data
}
As you can see I have defined my own composite type which consists of three lists. So you obviously can use it with only one list directly.
public class ArticleSubmitData {
private List<Article> singleArticles;
private List<Article> packageArticle;
private List<Article> popupArticles;
... // getter & setter, inner classes etc.
}
In my server page oder faclet I use the following html-code to make this work
...
<input id="" class="" type="text" name="singleArticles[${line.index}].engraving" />
...
So the trick is to define the variables on your webpage as an array and use this in your controller as a list. As you can see in my example I also use an inner class in my composite class which has extra attributes.
I am trying to get a List of custom object of type linked list into html using Sightly. But I a unable to read them in sightly. Sample Code is pasted below:
Java Bean:
public class MiniNavBean {
private String fPath;
private String activeAttr;
public MiniNavBean(String fPath, String activeAttr){
this.fPath = fPath;
this.activeAttr = activeAttr;
}
public String getFpath() {
return fPath;
}
public void setFpath(String fpath) {
this.fPath = fpath;
}
public String getActiveattr() {
return activeAttr;
}
public void setActiveattr(String activeattr) {
this.activeAttr = activeattr;
}
}
Java class which extends WCMUsePojo:
public class MiniNav extends WCMUsePojo {
private List<MiniNavBean> navList;
MiniNavBean miniNav;
public List<MiniNavBean> getNavList() {
return navList;
}
public void setNavList(List<MiniNavBean> navList) {
this.navList = navList;
}
#Override
public void activate() {
navList = new LinkedList<MiniNavBean>();
fPath = "fpaths";
activeAttr = "activeattrs;"
miniNav = new MiniNavBean(fpath, activeattr);
navList.add(miniNav);
}
}
Html file (Sightly):
<div data-sly-include="/apps/project/components/global.jsp"></div>
<div data-sly-use.mininav="com.components.MiniNav" data-sly-unwrap>
<div data-sly-list.navlist="${mininav.navList}">
<li>
<p>${navlist.fPath}</p>
<p>${navlist.activeAttr}</p>
</li>
</div>
When I am trying to execute the above code, I am able to see the linked list getting instantiated with the data in the java class. However when I am trying to display the values of the list in the front end, sightly is unable to read it.
Since the LinkedList is of CustomObject type(MiniNavBean) I suspect sightly is unable to read it as it doesn't know about this bean because we didn't refer that bean anywhere. Is there a way to fix this using sightly tags and read the data ?
Sightly would loop over Java objects too. I don't think it is issue with Sightly. Looks like your getters are wrong. Change your bean as shown below
public class MiniNavBean {
private String fPath;
private String activeAttr;
public MiniNavBean(String fPath, String activeAttr){
this.fPath = fPath;
this.activeAttr = activeAttr;
}
public String getfPath() {
return fPath;
}
public void setfPath(String fPath) {
this.fPath = fPath;
}
public String getActiveAttr() {
return activeAttr;
}
public void setActiveAttr(String activeAttr) {
this.activeAttr = activeAttr;
}
}
If you do not wish to change the bean, then you can access the getters directly in the Sightly file and check if it is working fine.
<div data-sly-include="/apps/project/components/global.jsp"></div>
<div data-sly-use.mininav="com.components.MiniNav" data-sly-unwrap>
<div data-sly-list.navlist="${mininav.navList}">
<li>
<p>${navlist.getFpath}</p>
<p>${navlist.getActiveattr}</p>
</li>
</div>
EDIT: To explain more based on the comments
You cannot access the fields which are private outside the class and are usually done using the public getter methods. However, in Sightly when you use the field name after the dot operator, you are not accessing the field directly, instead it calls the corresponding getter method based on the Java specification for naming getters / setters. So as per spec, your getters and setters were wrong in the bean due to which it didn't work.
Like I mentioned above, you can change only your bean and your code will work fine. Or you can leave your bean as is and change Sightly code to get things working.
In your example you are neither assigning a value to the navList member of MiniNav nor adding the MiniNavBean instance to it.
Add the following lines to your activate() method:
this.navList = new LinkedList<>();
this.navList.add(navList);
Also, the Java getters and HTL/Sightly accessors are not properly aligned, ie: for getFpath() you should use navlist.fpath
In case you already have those, are you getting any compile or runtime errors from HTL/Sightly?
HTL/Sightly is generally using reflection to lookup properties of objects so it does not care much about their type.
I'm sending this parameter to my struts action
cdata[1]=bar
In my action I'm interested in the index and the value.
I defined a getter/setter pair for CDATA as the OGNL documentation suggests:
public void setCdata(int index, String value){
LOG.info("setData; key="+ key +"; value="+ value);
// store index and value;
}
public String getCdata(int index){
return null; // don't really need a setter
}
This is the Exception I get:
2013-04-29 15:38:49,792 [http-apr-8080-exec-3] WARN com.opensymphony.xwork2.util.logging.commons.CommonsLogger.warn(CommonsLogger.java:60) - Error setting expression 'cdata[1]' with value '[Ljava.
lang.String;#4223d2a4'
ognl.OgnlException: target is null for setProperty(null, "1", [Ljava.lang.String;#4223d2a4)
at ognl.OgnlRuntime.setProperty(OgnlRuntime.java:2309)
at ognl.ASTProperty.setValueBody(ASTProperty.java:127)
at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
at ognl.SimpleNode.setValue(SimpleNode.java:301)
at ognl.ASTChain.setValueBody(ASTChain.java:227)
at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
at ognl.SimpleNode.setValue(SimpleNode.java:301)
at ognl.Ognl.setValue(Ognl.java:737)
...
If I define a public member variable String[] cdata = new String[1000] I don't see any exception in my log but my setter is not called either. If the member variable is private I get another exception again.
Use the following setup
List<String> cdata = new ArrayList<String>();
public List<String> getCdata() {
return cdata;
}
public void setCdata(final List<String> cdata) {
if (cdata == null) {
this.cdata = new ArrayList<String>();
} else {
this.cdata = cdata;
}
}
submit the values from JSP like cdata[1]=value etc
only requirement is to have the getters/setters. I've tested this Tomcat7 running on java 1.6. You can submit values like cdata[0], cdata[1] likewise
or else you could use a map
private Map<String, String> data = new HashMap<String, String>();
public Map<String, String> getData() {
return data;
}
public void setData(Map<String, String> data) {
this.data = data;
}
JSP can have
<s:form action="indexProperty">
<h3>Test The Map</h3>
<input type="text" name="data['0']"/>
<input type="text" name="data['1']"/>
<s:iterator value="data.entrySet()" var="aData">
<s:property value="#aData.key" />-<s:property value="#aData.value" />
</s:iterator>
<input type="submit" name="submit" value="submit"/>
</s:form>
Gets populated without a issue
My solution (rather an ugly hack):
I made my action class implement ServletRequestAware and in the action iterate over the parameter map from HttpServletRequest, fetch cdata from it and parse it for index and value
I had to change the sent parameter and encode eg cdata[999]=foobar like cdata_999_=foobar because if it looks like an array field struts requires there's a setter/getter for it in the action class.
According to the docs, OGNL provides support for indexing properties of JavaBeans: OGNL Reference Guide:
JavaBeans supports the concept of Indexed properties. Specifically this means that an object has a set of methods that follow the following pattern:
public PropertyType[] getPropertyName();
public void setPropertyName(PropertyType[] anArray);
public PropertyType getPropertyName(int index);
public void setPropertyName(int index, PropertyType value);
You didn't implement all of these methods. Also if you didn't initialize an array, the values could not be set.
You can read more about indexed properties here.
Indexed Properties
An indexed property is an array instead of a single value. In this case, the bean class provides a method for getting and setting the entire array. Here is an example for an int[] property called testGrades:
public int[] getTestGrades() {
return mTestGrades;
}
public void setTestGrades(int[] tg) {
mTestGrades = tg;
}
For indexed properties, the bean class also provides methods for getting and setting a specific element of the array.
public int getTestGrades(int index) {
return mTestGrades[index];
}
public void setTestGrades(int index, int grade) {
mTestGrades[index] = grade;
}
I have a command object associated with a spring form controller:
public class PluginInstance {
private Set<PluginParameter> pluginParameters = new HashSet<PluginParameter>();
... some other string/long properties and getter setters...
}
the PluginParameter also have a Set in it which contain the values
public class PluginParameter {
private String parmName;
private Set<PluginParmvalue> pluginParmvalues = new HashSet<PluginParmvalue>();
...some other string/long properties and getter setters...
}
(Normally the pluginParmvalues will contain only one value, a list have been used for future expandability)
In the spring form I binding the values as
<form:input path="pluginParameters[${itemsRow.index}].pluginParmvalues[0].parmValue" />
but the thing is that there can be a form:select(to present multiple predefined options to the user) or form:input (user can input any value). This has to be decided from another object
public class PluginConfigParm {
private String parmName;
private ArrayList<String> choices;
...getter setters and other properties
}
where I have to compare the name of PluginConfigParm.paramName with PluginParameter.paramName when they match and PluginConfigParm.choices.size() > 0 then form:select will be shown populated with the values from PluginConfigParm.choices otherwise form:input will be shown.
The question is simple: How can I do that.
Any help will be highly appreciated.
By using List<> instead of Set<> in controller. Problem solved. May be Set<> has no getter/setter that can be bind with spring form.
So <form:input path="pluginParameters[${itemsRow.index}].pluginParmvalues[0].parmValue" />
and List<> in controller makes my life easier.
The Set is not an indexed collection so I could not work by using this syntax
pluginParameters[${itemsRow.index}].pluginParmvalues[0].parmValue
eg:
class person{
set name<string> = new HashSet<String>()
}
<input type="hidden" path="person.name" name="person.name" value="<%=valueStr%>"/>
take valueStr = "hello, world"
by giving it as comma seperated values.. set works
Its working for me