I am writing a Gradle project with Springfox-swagger-ui 3.0.0 to present nice swagger UI with Api documentation.
compileOnly "io.springfox:springfox-swagger-ui:3.0.0"
One of api endpoints returns a POJO class, that is an auto-generated class from .xsd schema using XJC task.
What I'd like to do is to prevent Swagger from including auto-generated Xml-annotations
Example POJO class (generated from xsd schema):
#Getter
public class TestPojo {
#XmlElement(required = true)
protected String machineSerialNumber;
}
Generated api-docs:
"TestPojo": {
"type": "object",
"title": "TestPojo",
"properties": {
"machineSerialNumber": {
"type": "string",
"xml": {
"name": "machineSerialNumber",
"attribute": false,
"wrapped": false
}
}
}
}
What I'd like to achieve, is to not present these xml-annotations, so that api-docs content will look like this:
"TestPojo": {
"type": "object",
"properties": {
"machineSerialNumber": {
"type": "string"
}
},
"title": "TestPojo"
}
And swagger-ui model:
The problem here is that I cannot change the content of auto-generated classes to remove these annotations.
Is there a way to configure Swagger in a way so that it skips these annotations?
I have created one Model class(Product),it contains two feilds name and value.
I would like to give swagger default values for Product model. I was able to do it with the help of #ApiModelProperty annotation and it's working fine.
But my actual requirement is to give multiple values for same object something like this
{
"products": [
{
"name": "X",
"value": "100"
},
{
"name": "Y",
"value": "100"
},
{
"name": "Z",
"value": "100"
},
{
"name": "A",
"value": "01"
}
]
}
this default model I am trying to create for HTTP POST request how can I achieve this with swagger2 and springboot
You should mark the filed with datatype="List"
public class MyClass {
....
#ApiModelProperty(datatype="List", example = "'['{'name': 'X','value': '100'}']")
private List< Product> products;
....
}
Edit
public class MyClass {
....
#ApiModelProperty(value = "{\n'name':'X',\n'value':'100'\n},\n{\n'name':'Y',\n'value':'100'\n}")
private List< Product> products;
....
}
So I'm working on building a lil website that's supposed to showcase insects and some info about em, and I've gotten the fetch call and the site itself up and running, no problem. On the back-end I have an API with a Spring Boot that retrieves my InsectObject, which for now just holds a title and description string and that works fine.
Now, excuse me as I try to explain the issue to the best of my abilities.
My problem is that I'm getting a response as follows from my API:
[
{
"id": 1,
"title": "mantis",
"description": "leafy boi"
},
{
"id": 2,
"title": "moth",
"description": "fly boi"
}
]
Wheres I want it to return it as:
{
bugs: [
{
"id": 1,
"title": "mantis",
"description": "leafy boi"
},
{
"id": 2,
"title": "moth",
"description": "fly boi"
}
]
}
Which is how I think a proper api call should look like. But then again, it's the first time I venture into this territory and I've only been following tutorials and documentation, building my own picture along the way.
If it's of any relevance, my rest controller looks like this:
#RestController
public class BugSiteController {
private final InsectRepository repository;
BugSiteController(InsectRepository repository) {
this.repository = repository;
}
// get all bugs from the repo
#CrossOrigin(origins = "http://localhost:3000")
#GetMapping("/bugs")
List<InsectObject> getAll() {
return repository.findAll();
}
}
What am I missing? Is it something in my getAll() method I should change to get the desired result? or should I be able to work with the first result regardless? Should I maybe return something other than a List<>? I tried with ResponseEntity but had the exact same result, just way more verbose.
Thanks in advance.
You could set the value in Model or you could set it in a Map.
Model
class InsectResponse {
#JsonProperty("bug")
private List<InsectObject> insectObject;
// Getter, Setter & Constructor
}
Controller
#GetMapping("/bugs")
public ReponseEntity getAll() {
return ResponseEntity.ok(new InsectResponse(repository.findAll()));
}
or
#GetMapping("/bugs")
public ReponseEntity getAll() {
return ResponseEntity.ok(new HashMap<String, List<InsectObject>>() {{
put("bug", repository.findAll());
}});
}
On the InsectObject entity class add:
#JsonRootName(value = "bug")
I'm trying to implement some custom validation logic for a spring boot endpoint using a combination of JSR-303 Bean Validation API and Spring's Validator.
Based on the Validator class diagram it appears to be possible to extend one of CustomValidatorBean, SpringValidatorAdapter or LocalValidatorFactoryBean to add some custom validation logic into an overridden method validate(Object target, Errors errors).
.
However, if I create a validator extending any of these three classes and register it using #InitBinder its validate(Object target, Errors errors) method is never invoked and no validation is performed. If I remove #InitBinder then a default spring validator performs the JSR-303 Bean Validation.
Rest controller:
#RestController
public class PersonEndpoint {
#InitBinder("person")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new PersonValidator());
}
#RequestMapping(path = "/person", method = RequestMethod.PUT)
public ResponseEntity<Person> add(#Valid #RequestBody Person person) {
person = personService.save(person);
return ResponseEntity.ok().body(person);
}
}
Custom validator:
public class PersonValidator extends CustomValidatorBean {
#Override
public boolean supports(Class<?> clazz) {
return Person.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
super.validate(target, errors);
System.out.println("PersonValidator.validate() target="+ target +" errors="+ errors);
}
}
If my validator implements org.springframework.validation.Validator then its validate(Object target, Errors errors) method is called but JSR-303 Bean Validation is not performed prior to it. I can implement my custom JSR-303 validation similar to the way SpringValidatorAdapter implements its JSR-303 Bean Validation but there has to be a way to extend it instead:
#Override
public void validate(Object target, Errors errors) {
if (this.targetValidator != null) {
processConstraintViolations(this.targetValidator.validate(target), errors);
}
}
I have looked at using custom JSR-303 constraints to avoid using org.springframework.validation.Validator all together but there must be a way to make a custom validator work.
Spring validation documentation is not super clear on combining the two:
An application can also register additional Spring Validator instances per DataBinder instance, as described in Section 9.8.3, “Configuring a DataBinder”. This may be useful for plugging in validation logic without the use of annotations.
And then later on it touches on configuring multiple Validator instances
A DataBinder can also be configured with multiple Validator instances via dataBinder.addValidators and dataBinder.replaceValidators. This is useful when combining globally configured Bean Validation with a Spring Validator configured locally on a DataBinder instance. See ???.
I'm using spring boot 1.4.0.
This problem can be solved extending the LocalValidatorFactoryBean, you can override the validate method inside this class giving any behavior that you want.
In my case I need to use JSR-303 AND custom validators for same model in different methods in same Controller, normally is recommended to use #InitBinder, but it is not sufficient for my case because InitBinder make a bind between Model and Validator (if you use #RequestBody InitBinder is just for one model and one validator per Controller).
Controller
#RestController
public class LoginController {
#PostMapping("/test")
public Test test(#Validated(TestValidator.class) #RequestBody Test test) {
return test;
}
#PostMapping("/test2")
public Test test2(#Validated #RequestBody Test test) {
return test;
}
}
Custom Validator
public class TestValidator implements org.springframework.validation.Validator {
#Override
public boolean supports(Class<?> clazz) {
return Test.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Test test = (Test) target;
errors.rejectValue("field3", "weird");
System.out.println(test.getField1());
System.out.println(test.getField2());
System.out.println(test.getField3());
}
}
Class to be validate
public class Test {
#Size(min = 3)
private String field2;
#NotNull
#NotEmpty
private String field1;
#NotNull
#Past
private LocalDateTime field3;
//...
//getter/setter
//...
}
CustomLocalValidatorFactoryBean
public class CustomLocalValidatorFactoryBean extends LocalValidatorFactoryBean {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Override
public void validate(#Nullable Object target, Errors errors, #Nullable Object... validationHints) {
Set<Validator> concreteValidators = new LinkedHashSet<>();
Set<Class<?>> interfaceGroups = new LinkedHashSet<>();
extractConcreteValidatorsAndInterfaceGroups(concreteValidators, interfaceGroups, validationHints);
proccessConcreteValidators(target, errors, concreteValidators);
processConstraintViolations(super.validate(target, interfaceGroups.toArray(new Class<?>[interfaceGroups.size()])), errors);
}
private void proccessConcreteValidators(Object target, Errors errors, Set<Validator> concreteValidators) {
for (Validator validator : concreteValidators) {
validator.validate(target, errors);
}
}
private void extractConcreteValidatorsAndInterfaceGroups(Set<Validator> concreteValidators, Set<Class<?>> groups, Object... validationHints) {
if (validationHints != null) {
for (Object hint : validationHints) {
if (hint instanceof Class) {
if (((Class<?>) hint).isInterface()) {
groups.add((Class<?>) hint);
} else {
Optional<Validator> validatorOptional = getValidatorFromGenericClass(hint);
if (validatorOptional.isPresent()) {
concreteValidators.add(validatorOptional.get());
}
}
}
}
}
}
#SuppressWarnings("unchecked")
private Optional<Validator> getValidatorFromGenericClass(Object hint) {
try {
Class<Validator> clazz = (Class<Validator>) Class.forName(((Class<?>) hint).getName());
return Optional.of(clazz.newInstance());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
logger.info("There is a problem with the class that you passed to "
+ " #Validated annotation in the controller, we tried to "
+ " cast to org.springframework.validation.Validator and we cant do this");
}
return Optional.empty();
}
}
Configure application
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public javax.validation.Validator localValidatorFactoryBean() {
return new CustomLocalValidatorFactoryBean();
}
}
Input to /test endpoint:
{
"field1": "",
"field2": "aaaa",
"field3": "2018-04-15T15:10:24"
}
Output from /test endpoint:
{
"timestamp": "2018-04-16T17:34:28.532+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"weird.test.field3",
"weird.field3",
"weird.java.time.LocalDateTime",
"weird"
],
"arguments": null,
"defaultMessage": null,
"objectName": "test",
"field": "field3",
"rejectedValue": "2018-04-15T15:10:24",
"bindingFailure": false,
"code": "weird"
},
{
"codes": [
"NotEmpty.test.field1",
"NotEmpty.field1",
"NotEmpty.java.lang.String",
"NotEmpty"
],
"arguments": [
{
"codes": [
"test.field1",
"field1"
],
"arguments": null,
"defaultMessage": "field1",
"code": "field1"
}
],
"defaultMessage": "Não pode estar vazio",
"objectName": "test",
"field": "field1",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotEmpty"
}
],
"message": "Validation failed for object='test'. Error count: 2",
"path": "/user/test"
}
Input to /test2 endpoint:
{
"field1": "",
"field2": "aaaa",
"field3": "2018-04-15T15:10:24"
}
Output to /test2 endpoint:
{
"timestamp": "2018-04-16T17:37:30.889+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotEmpty.test.field1",
"NotEmpty.field1",
"NotEmpty.java.lang.String",
"NotEmpty"
],
"arguments": [
{
"codes": [
"test.field1",
"field1"
],
"arguments": null,
"defaultMessage": "field1",
"code": "field1"
}
],
"defaultMessage": "Não pode estar vazio",
"objectName": "test",
"field": "field1",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotEmpty"
}
],
"message": "Validation failed for object='test'. Error count: 1",
"path": "/user/test2"
}
I hope this help.
Per #M.Deinum - using addValidators() instead of setValidator() did the trick. I also agree that using JSR-303, #AssertTrue method-based annotation specifically for cross fields validation, is probably a cleaner solution. A code example is available at https://github.com/pavelfomin/spring-boot-rest-example/tree/feature/custom-validator. In the example, the middle name validation is performed via custom spring validator while last name validation is handled by the default jsr 303 validator.
I have a class where I have multiple field named results, (Actually I have 12 of them, but for the sake of the question, I just include 2 in this question)
public class APIRequest {
#JsonProperty("code")
public String code;
#JsonProperty("error")
public APIError error;
#JsonProperty("results")
public APILogin login;
#JsonProperty("results")
public APIUser user;
}
The reason I have this because my backend API call will always return the results field for every request
for example http://api.testapp.com/get_user_profile would return this JSON
The results key would then be mapped by APIUser class
{
"code": "200",
"results": {
"name": "Jackson Liu"
"age": "21"
"first_name": "Jackson"
"last_name": "Liu"
}
}
And then http://api.testapp.com/login would return this JSON
The results key would then be mapped by APILogin class
{
"code": "200",
"results": {
"token": "12u3912edsdnisnknaklsmdlsadmsalma"
"session_id": "ladlwjopwjwpdmdlndlkadlaasassa"
"state": "1"
}
}
And because of that, I will get this error.
com.fasterxml.jackson.databind.JsonMappingException: Multiple fields
representing property "results":
id.testapp.android.testapp.jsonobjects.APIResults#login vs
id.testapp.android.testapp.jsonobjects.APIResults#user
Any thoughts on how should I fix this?
To make it simple use MAP. Jackson will take care of populating MAP. Just provide setter and getter for each field. And Depends on your context you can read required field in Map
public class APIRequest {
#JsonProperty("code")
public String code;
#JsonProperty("error")
public APIError error;
#JsonProperty("results")
Map<String, String> results;
}