I have the following Spring MVC Rest controller:
#ResponseStatus(HttpStatus.OK)
#RequestMapping(value = "/zoek", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody
JsonExcelLijstList zoek(ZoekExcelLijstParameters parameters) {
// arbitraty code
}
The ZoekExcelLijstParameters object looks like this:
public class ZoekExcelLijstParameters extends NgTableParams {
private String typeSleutel;
private String status;
private Date datumVan;
private Date datumTot;
// getters and setters
}
and the NgTableParams looks like this
public class NgTableParams {
private int page = 1;
private int count = 20 ;
private HashMap<String, String> sorting; // HashMap because Jackson doesn't like interfaces (?)
// getters and setters
}
I've already configured the Jackson Message Converter:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
The data that I'm sending using Angular.js looks like this (as represented by Chrome dev tools, so this is not JSON)
count:10
dateTot:
dateVan:
page:1
sorting:{"uploadDate":"asc", "uploader": "asc"}
status:
type:
When I do this, I get the following error:
BindException:org.springframework.validation.BeanPropertyBindingResult:
1 errors↵Field error in object 'zoekExcelLijstParameters' on field
'sorting': rejected value [{"uploadDate":"asc"}]; codes
[typeMismatch.zoekExcelLijstParameters.sorting,typeMismatch.sorting,typeMismatch.java.util.HashMap,typeMismatch];
arguments
[org.springframework.context.support.DefaultMessageSourceResolvable:
codes [zoekExcelLijstParameters.sorting,sorting]; arguments [];
default message [sorting]]; default message [Failed to convert
property value of type 'java.lang.String' to required type
'java.util.HashMap' for property 'sorting'; nested exception is
java.lang.IllegalStateException: Cannot convert value of type
[java.lang.String] to required type [java.util.HashMap] for property
'sorting': no matching editors or conversion strategy found]
I have no idea what I'm doing wrong. I've also tried using a List with key-value objects instead of a HashMap.
I've seen many solutions where they suggest to work with POST data and a request body, or just plain HashMap's, but here I need (want) it to work with a GET (since it's a 'get' operation and not a 'create' operation).
Sorry I didn't have the time to rewrite your code but here is a solution i used for a different project: Just put them in a map object
#RestController
public class NewServiceController
{
#Autowired
EsbcoreServiceRepository serviceRepo;
#Autowired
EsbcoreRuleRepository ruleRepo;
#Autowired
EsbcoreRuleConditionRepository ruleConditionRepo;
#Autowired
EsbcoreRuleDestinationRepository ruleDestinationRepo;
#Autowired
EsbcoreServiceDestinationRepository serviceDestinationRepo;
#GetMapping(value="/api/allservices")
public Map<String, Object> getAllServices()
{
Map<String, Object> servicesMap=new HashMap<String, Object>();
servicesMap.put("services", serviceRepo.findAll());
servicesMap.put("rules", ruleRepo.findAll());
servicesMap.put("ruleConditions", ruleConditionRepo.findAll());
servicesMap.put("ruleDestinations", ruleDestinationRepo.findAll());
servicesMap.put("serviceDestinations", serviceDestinationRepo.findAll());
return servicesMap;
}
Related
I am using Java Spring Boot #RestController with an object containing enum fields.
Spring automagically deserializes the JSON to the MyRequest object.
#RestController
public class MyController {
#PostMapping(path = "/operation")
public ResponseEntity<MyResponse> operation(#Valid #RequestBody MyRequest request) {
...
}
}
public class MyRequest {
private MyEnum1 field1;
private MyEnum2 field2;
private MyEnum3 field3;
private MyEnum4 field4;
private MyEnum5 field5;
private MyEnum6 field6;
... // really a lot of various enum fields!
}
public enum MyEnum1 {
VAL1, VAL2, VAL3;
}
The problem is that if the JSON contains completely invalid value of the enum field, the deserializer silently converts them to null, without any exception.
{
"field1": "BLAHBLAH",
...
}
This is user-unfriendly and treacherous.
I know that I may write custom JSON deserializers for each enum, but the solution is cumbersome and non-elegant.
Is there a way to globally set the JSON enum deserializer to a "strict mode", so if the value is invalid it throws an exception? If so, how and where?
That feature should be disabled by default.
But if you want to set it explicitly you can do it like this:
in your properties:
spring.jackson.deserialization.read-unknown-enum-values-as-null=false
or as an alternative in a configuration class (actually any bean would work, just make sure it happens early):
#Autowired
public void configureJackson(ObjectMapper objectMapper) {
objectMapper.disable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
}
Because it should actually be like this by default, I am wondering why it is not for you. Do you enable it somewhere? Which Spring Boot version are you using?
Following a advice from Spring Boot integration tests doesn't read properties files I created the following code, with the intention of reading a map from properties in my JUnit test.
(I am using yml format, and using #ConfigurationProperties instead of #Value)
#RunWith(SpringJUnit4ClassRunner.class)
#TestPropertySource(locations="classpath:application-test.yml")
#ContextConfiguration(classes = {PropertiesTest.ConfigurationClass.class, PropertiesTest.ClassToTest.class})
public class PropertiesTest {
#Configuration
#EnableConfigurationProperties
static class ConfigurationClass {
}
#ConfigurationProperties
static class ClassToTest {
private String test;
private Map<String, Object> myMap = new HashMap<>();
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
public Map<String, Object> getMyMap() {
return myMap;
}
}
#Autowired
private ClassToTest config;
#Test
public void testStringConfig() {
Assert.assertEquals(config.test, "works!");
}
#Test
public void testMapConfig() {
Assert.assertEquals(config.myMap.size(), 1);
}
}
My test configuration (in application-test.yml):
test: works!
myMap:
aKey: aVal
aKey2: aVal2
Strangely, the String "works!" is successfully read from the config file, but the map is not populated.
What am I missing?
Note: adding a map setter causes the following exception:
Caused by: org.springframework.validation.BindException: org.springframework.boot.bind.RelaxedDataBinder$RelaxedBeanPropertyBindingResult: 1 errors
Field error in object 'target' on field 'myMap': rejected value []; codes [typeMismatch.target.myMap,typeMismatch.myMap,typeMismatch.java.util.Map,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [target.myMap,myMap]; arguments []; default message [myMap]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Map' for property 'myMap'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Map' for property 'myMap': no matching editors or conversion strategy found]
at org.springframework.boot.bind.PropertiesConfigurationFactory.checkForBindingErrors(PropertiesConfigurationFactory.java:359)
at org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget(PropertiesConfigurationFactory.java:276)
at org.springframework.boot.bind.PropertiesConfigurationFactory.bindPropertiesToTarget(PropertiesConfigurationFactory.java:240)
at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:330)
... 42 more
After some wonderful time with a debugger,
I believe that this is a bug / missing feature in TestPropertySourceUtils.addPropertiesFilesToEnvironment():
try {
for (String location : locations) {
String resolvedLocation = environment.resolveRequiredPlaceholders(location);
Resource resource = resourceLoader.getResource(resolvedLocation);
environment.getPropertySources().addFirst(new ResourcePropertySource(resource));
}
}
ResourcePropertySource can only deal with .properties files and not .yml.
In regular app, YamlPropertySourceLoader registered and can deal with .yml.
As a note:
TestPropertySourceUtils.addPropertiesFilesToEnvironment() is called by:
org.springframework.test.context.support.DelegatingSmartContextLoader.prepareContext()
(inherited from AbstractContextLoader)
DelegatingSmartContextLoader is the default context loader you receive if no loader is specified in #ContextConfiguration.
(in fact #ContextConfiguration specifies an interface, but AbstractTestContextBootstrapper.resolveContextLoader() changes it to a concrete class)
To resolve the problem, I changed my configuration to application-test.properties
and used that file in my test.
test=works!
myMap.aKey: aVal
Another comment: the setter on the map is NOT needed:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-loading-yaml
To bind to properties like that using the Spring DataBinder utilities
(which is what #ConfigurationProperties does) you need to have a
property in the target bean of type java.util.List (or Set) and you
either need to provide a setter, or initialize it with a mutable
value, e.g. this will bind to the properties above
I am sending the following JSON object to my Java Spring application
{
"ewsUrl":"https://dummy.url.com",
"ewsIdentityToken":"12345",
"itemIds":["itemids"],
"entityId":null,
"documentType":"Dummy",
"documentStatus":"Record",
"filename":"message.eml",
"metadata":{"title":"message"}
}
I have defined an object public class RequestObject and in my controller I have
public RequestObject
testMyStuff(#CurrentUser User currentUser,
#RequestBody RequestObject myDummyObject) throws Exception {
return myDummyObject
}
My application returns the error Could not read document: Root name 'ewsUrl' does not match expected ('RequestObject') for type...etc
However if I send the JSON formatted like this it successfully maps the object:
{ "RequestObject":
{
"ewsUrl":"https://dummy.url.com",
"ewsIdentityToken":"12345",
"itemIds":["itemids"],
"entityId":null,
"documentType":"Dummy",
"documentStatus":"Record",
"filename":"message.eml",
"metadata":{"title":"message"}
}
}
I do not want to name the object in my JSON, I want to send as described in the first example. How do I achieve this?
UPDATE:
RequestObject.java
public class RequestObject {
public String ewsUrl;
public String ewsIdentityToken;
public String[] itemIds;
public String entityId;
public String documentType;
public String documentStatus;
public String filename;
public Metadata metadata;
public RequestObject() {
}
public static class Metadata {
public String title;
}
}
UPDATE2:
The way it is described in this example suggests that the object does not need to be named in the JSON data in the POST request. I think I am emulating this example, but I'm getting different results.
Is there a configuration for Jackson/Spring that I am missing?
Update 3:
The complete error message is:
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException:
Could not read document:
Root name 'ewsUrl' does not match expected ('RequestObject') for type
[simple type, class uk.co.test.RequestObject] at [Source:
java.io.PushbackInputStream#7e223182; line: 2, column: 9];
nested exception is com.fasterxml.jackson.databind.JsonMappingException:
Root name 'ewsUrl' does not match expected ('RequestObject') for type
[simple type, class uk.co.test.RequestObject]
There's some configuration settings that look like they can be defined for the ObjectMapper that controls the behaviour of the root element:
https://fasterxml.github.io/jackson-databind/javadoc/2.0.0/com/fasterxml/jackson/databind/DeserializationFeature.html#UNWRAP_ROOT_VALUE - see SerializationFeature.html#WRAP_ROOT_VALUE as well.
UNWRAP_ROOT_MODULE is disabled by default according to the docs so not sure why you're seeing the behaviour you are.
Config example for spring is available at
http://docs.spring.io/spring-framework/docs/3.2.3.RELEASE/javadoc-api/org/springframework/http/converter/json/JacksonObjectMapperFactoryBean.html
Just use JSONArray instead of JSONObject
Update
You can get your Json Object via JSONArray.getJSONObject()
My problem is with having Spring bind the data I get from a form to a JPA entity. The wierd part is, it works just fine if I do not look at the BindingResults. The BindingResults says there were binding errors when an empty string is passed in for the field graduation, but I know it does bind them correctly because when I don't check Hibernate updates the database perfectly. Is there a way to not have to write logic to circumnavigate the wrongly fired binding errors?
#Entity
#Table(name="child")
public class Child {
#Id
#Column(name="id")
private Integer childId;
#ManyToOne(fetch=FetchType.EAGER )
#JoinColumn(name="house", referencedColumnName="house")
private House house;
#NotNull()
#Past()
#Column(name="birthday")
private Date birthday;
#Column(name="graduation_date")
private Date graduationDay;
}
I have tried the following lines in a property editor to no avail
SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd, yyyy");
registry.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
Here is the method signature for the controller method Handling the request
#Controller
#SessionAttributes(value="child")
#RequestMapping(value="child")
public class ChildModController {
#RequestMapping(value="save-child.do", params="update", method = RequestMethod.POST)
public #ResponseBody Map<String,?> updateChild(
HttpServletRequest request,
#Valid #ModelAttribute(value="child")Child child,
BindingResult results)
}
This is what I get from the BindingResult class as a message
09:01:36.006 [http-thread-pool-28081(5)] INFO simple - Found fieldError: graduationDay,
Failed to convert property value of type java.lang.String to required type java.util.Date for property graduationDay;
nested exception is org.springframework.core.convert.ConversionFailedException:
Failed to convert from type java.lang.String to type #javax.persistence.Column java.util.Date for value ';
nested exception is java.lang.IllegalArgumentException
Spring automatically binds simple object types like String and Number, but for complex objects like java.util.Date or your own defined types, you will need to use what is called a PropertyEditors or Converters, both could solve your problem.
Spring already has a predefiend PropertyEditors and Converters like #NumberFormat and #DateTimeFormat
You can use them directly on your fields like this
public class Child {
#DateTimeFormat(pattern="dd/MM/yyyy")
private Date birthday;
#DateTimeFormat(iso=ISO.DATE)
private Date graduationDay;
#NumberFormat(style = Style.CURRENCY)
private Integer myNumber1;
#NumberFormat(pattern = "###,###")
private Double myNumber2;
}
Spring also allows you to define your own type converters which you must use it combined with Spring ConversionService
For example if you have a Color class like this
public class Color {
private String colorString;
public Color(String color){
this.colorString = color;
}
}
You would define the color converter for example like this
public class StringToColor implements Converter<String, Color> {
public Color convert(String source) {
if(source.equal("red") {
return new Color("red");
}
if(source.equal("green") {
return new Color("green");
}
if(source.equal("blue") {
return new Color("blue");
}
// etc
return null;
}
}
To check more about converters check this, also check this to know the difference between Converters and PropertyEditors
Lets say I have a list of objects like this: LinkedList<JsonAssessment> jsonAssessments....
It is returned to this kind of method:
#RequestMapping(value = "mapping", method = RequestMethod.POST)
public
#ResponseBody
List<JsonAssessment> doSomething(....) {
.....
}
I am making AJAX call to this controller everything is working correctly as expected but I don't like the naming my JSON is returned. In firebug I am seeing:
{"LinkedList":[{"assessmentName":"........
The question is how can I rename that root element LinkedList? Is there any config I have to set?
EDIT
I do not want to use any wrapper objects.
EDIT
My ObjectMapper:
public class JsonObjectMapper extends ObjectMapper {
public JsonObjectMapper() {
super();
this.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
this.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
this.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
}
Spring: 3.2.4.RELEASE
Jackson: 2.1.2
EDIT
This object mapper is declared in MVC message converters:
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jsonObjectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
And that is what I've tried with naming strategy:
public class JsonPropertyNamingStrategy extends PropertyNamingStrategy {
public static final MyNamingStrategy MY_NAMING_STRATEGY = new MyNamingStrategy();
private static final LinkedHashMap<String, String> PROPERTIES = new LinkedHashMap<String, String>() {{
put("LinkedList", "resultList");
}};
public static class MyNamingStrategy extends PropertyNamingStrategyBase {
#Override
public String translate(String propertyName) {
if (propertyName == null) {
return null;
}
if (PROPERTIES.containsKey(propertyName)) {
return PROPERTIES.get(propertyName);
}
return propertyName;
}
}
}
I was debugging it and when it comes to translate method property names comes all except the root element. I have everything that LinkedList contains, but LinkedList is not coming to this method.
Instead of directly returning a the List, wrap it inside a ResponseEntity that will give you a response without a root element
#RequestMapping(value = "mapping", method = RequestMethod.POST)
public
#ResponseBody ResponseEntity<List<JsonAssessment>> doSomething(....) {
.....
return new ResponseEntity(yourList);
}
That way you don't have a root element. If you still want a root element you could add it to a Map. Results in the following JSON "[{"assessmentName":"........]
#RequestMapping(value = "mapping", method = RequestMethod.POST)
public
#ResponseBody ResponseEntity<Map<String, List<JsonAssessment>>> doSomething(....) {
.....
Map results = new HashMap();
result.put("assesments", yourlist);
return new ResponseEntity(results);
}
Should output {"assesments":[{"assessmentName":"........
Although you are stilling wrapping the objects here, it is in objects that are freely available, you don't have to add your own custom classes.
This is what we are using in a couple of our #Controllers, we are using Spring 3.2 and Jackson 2.2.
put the list in a wrapper class is a a commonly implemented strategy
If you are using Jackson Mapper you can use Annotations to define names of properties in classes by
#JsonProperty("foo")
or set the order by
#JsonPropertyOrder({"foo", "bar"})
and so on.
See further Jackson Annotations
edit:
Sorry, just saw the wrapper-comment. The only solution i saw is using a wrapper like this: How to rename root key in JSON serialization with Jackson