If I have the following class:
#ApiModel
public class SomeEvent {
#ApiModelProperty(
value = "The unique identifier of this event.",
example = "MY_EVENT"
)
private final String eventType;
#JsonCreator
public SomeEvent(
#JsonProperty(value = "eventType", required = true) String eventType) {
this.eventType = eventType;
}
public String getEventType() {
return eventType;
}
}
I would like to use Swagger to generate an instance and serialize it to the following JSON:
{
"eventType": "MY_EVENT"
}
By using this class as a #RequestBody in my Spring Boot controller, the expected serialized form is displayed perfectly on the Swagger UI.
However, I'd like to be able to generate an instance (where the instance's fields contain the values from the example in the #ApiModelProperty) using code. I've searched around and could not find a single example of how to do this, assuming it's possible to achieve.
Note: I do not want to just create an instance using new SomeEvent("MY_EVENT");, but rather use Swagger to grab the examples.
Related
I am calling a third party REST endpoint. The request for the thrid party REST endpoint looks like this.
{
"body": {
"accountNumber": "12345"
},
"header": {
"username": "someusername",
"password": "somepassword"
}
}
I have created 3 bean classes
MyRequest.java
#Builder
#JsonDeserialize(builder = MyRequest.MyRequestBuilder.class)
public class MyRequest {
#JsonProperty("header")
private MyHeader header;
#JsonProperty("body")
private MyBody body;
}
MyBody.java
#Getter
#Builder
public class MyBody {
private String accountNumber;
}
MyHeader.java
#Getter
#Builder
public class MyHeader {
private String username;
private String password;
}
I'm creating request object using
MyBody body = MyBody.builder().accountNumber("12345").build();
MyHeader header = MyHeader.builder().username("someusername").password("somepassword").build();
MyRequest request = MyRequest.builder().body(body).header(header).build();
I'm calling the 3rd part REST endpoint using the below code
HttpEntity<MyRequest> httpEntity = new HttpEntity<>(myRequest, httpHeaders);
String url = "someurl";
someResponse = this.restTemplate.postForObject(url, httpEntity,
SomeResponse.class);
I'm getting proper response. But if I remove #Getter annotation from MyHeader and MyBody, 3rd party REST endpoint is getting null values in request. Why #Getter is necessary here. How to make this work without #Getter.
if I remove #Getter annotation from MyHeader and MyBody, 3rd party REST endpoint is getting null values in request. Why #Getter is necessary here. How to make this work without #Getter.
You need to instruct Jackson somehow which data should be included during serialization. The default mechanism is to use getters for that purpose.
That's not the only way.
Alternatively, you can annotate certain fields with #JsonProperty, or change the default visibility of the fields either globally or for a particular type using annotation #JsonAutoDetect and set its property fieldVisibility to ANY (that would make the fields discoverable even in the absence of getters)
The key point is that the information on which data needs to be present in the serialized JSON should be provided somehow, and it doesn't matter how exactly.
Consider a dummy POJO with no getters:
#AllArgsConstructor
public class Foo {
#JsonProperty
private String bar;
}
Property bar would be reflected in the resulting JSON.
ObjectMapper mapper = new ObjectMapper();
Foo foo = new Foo("baz");
String jsonFoo = mapper.writeValueAsString(foo);
System.out.println(jsonFoo);
Output:
{"bar":"baz"}
Now, if we remove #JsonProperty (no getters as before) that's what would happen.
#AllArgsConstructor
public class Foo {
private String bar;
}
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
Foo foo = new Foo("baz");
String jsonFoo = mapper.writeValueAsString(foo);
System.out.println(jsonFoo);
Output:
{}
An empty Bean produces an empty JSON (or raises an exception, depending on configuration). And vise versa, Deserialization of an empty JSON gives an empty Bean.
I am developing a Java backend application using Spring Boot. My controller needs to receive this type of object as input with an HTTP POST call:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Input {
private String name;
private String surname;
private String fiscalCode;
}
obviously these data arrive thanks to a JSON made like this:
{
"name":"John",
"surname":"Smith",
"fiscalCode":"XXXXXXXXX"
}
This is an example of a controller handling POST containing this body:
#PostMapping(produces = { "application/json" })
public ResponseEntity<Object> myController(#RequestBody Input myInput) {
// code....
}
The question is: how can I elegantly (without using a switch case or a series of if-else) detect all fields that are "" or null in my myInput object, and return a string that warns for example that "name, surname fields missing"?
What is the best way to do this? I've heard it's okay to use #ControllerAdvice, but I don't know how to do it.
Check out the #Validate annotation.
e.g. https://www.baeldung.com/spring-boot-bean-validation
what I am trying to do is,
If I take one pojo class like
#Entity
#Table(name = "property_table")
public class Property {
#Id
#Column(name = "property_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int propertyId;
#Column(name = "property_name")
private String propertyName;
#Column(name = "property_type")
private String propertyType;
}
In RestController I wrote Two Methods like
#GetMapping(value = "/getProperties", produces = { "application/json",
"application/xml" }, consumes = { "application/xml", "application/json" })
#ResponseBody
public List<Property> getProperties() {
//some code
}
#GetMapping(value = "/getPropertyById", produces = { "application/json",
"application/xml" }, consumes = { "application/xml", "application/json" })
#ResponseBody
public Property getPropertyById() {
//some code
}
So, hear what I am trying to do is
for first api method I want return json like some parameters from Property pojo class i.e., like
for getProperties api method
{
"property":[
{
"propertyId":001,
"propertyName":"PROPERTY 1"
},
{
"propertyId":002,
"propertyName":"PROPERTY 2"
}
],
In the Above json I want to return only two parameters i.e propertyId,propertyName and remaining parameter i.e propertyType I dont want to retun in json.
How to return like that?
and for the second api method I want to return all three parameters. i.e., like below
for getPropertyById api method
{
"propertyId":001,
"propertyName":"PROPERTY 1",
"propertyType:"PROPERTY_TYPE 1"
},
how to maintain different json response using same pojo class with different parameters for different api methods.
please help me to solve this isuue.
Thanks.
REST API under/over-fetching is a well-known problem. There's only two (classical ways) to handle that.
The first one is to build one model per each attribute visibility state. So, in your case, you'll need to create two different models (this kind of models are called DTO - Data Transfert Object). One model will have a propertyType attribute, the other will not. The model Property you've shared shows that you use the same class as entity and as transfert object. This solution will add some complexity to your app because you will have to implement some mappers to convert your entity to a corresponding DTO.
The second one is to accept that you send an attribute that will not be useful (be aware of the over-fetching). This solution is often the most adopted one. The cons of this solution is when you don't want to send something to your client (imagine a User model, you want to get the password from your client but you don't want to sent it back to it). Another obvious negative point is that the transactions will be larger but it is negligible in most cases
I would strongly advice you to keep your #Entity isolated in the 'db' layer. So that changes on the database side don't affect your API and vice versa. Also, you will have much better control over what data is exposed in your API. For your needs you can create 2 true DTOs, like PropertyDto and PropertyDetailsDto (or using private fields and getters/setters).
public class PropertyDto {
public String propertyId;
public String propertyName;
}
public class PropertyDetailsDto extends PropertyDto {
public String propertyType;
}
Map your #Entity to a specific dto corresponding to your needs.
EDIT
public List<PropertyDto> getProperties() {
return toPropertyDtos(repository.findAll());
}
public PropertyDetailsDto getPropertyById(Long id) {
return toPropertyDetailsDto(repository.findBy(id));
}
in some Mapper.java
...
public static List<PropertyDto> toPropertyDtos(List<Property> properties) {
return properties.stream()
.map(Mapper::toPropertyDto)
.collect(toList());
}
private static PropertyDto toPropertyDto(Property property) {
PropertyDto dto = new PropertyDto();
dto.propertyId = property.propertyId;
dto.propertyName = property.propertyName;
return dto;
}
// same stuff for `toPropertyDetailsDto`, you could extract common mapping parts in a separate private method inside `Mapper`
...
Given a RESTful web service developed using the Spring Boot framework, I wanted a way to suppress the birthDate of all Users in the response. This is what I implemented after looking around for a solution :
#RestController
public class UserResource {
#Autowired
private UserDAOservice userDAOService;
#GetMapping("/users")
public MappingJacksonValue users() {
List<User> users = userDAOService.findAll();
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name");
FilterProvider filters = new SimpleFilterProvider().addFilter(
"UserBirthDateFilter", filter);
MappingJacksonValue mapping = new MappingJacksonValue(users);
mapping.setFilters(filters);
return mapping;
}
}
However, when I hit the rest end point in the browser, I can still see the birth date of the user in the response :
{
"id": 1,
"name": "Adam",
"birthDate": "1980-03-31T16:56:28.926+0000"
}
Question 1 : What API can I use to achieve my objective?
Next, assuming that I want to adhere to HATEOAS in combination with filtering, how can I go about doing this. I am unable to figure out the APIs that can be used for using these two features together :
#GetMapping("/users/{id}")
public EntityModel<User> users(#PathVariable Integer id) {
User user = userDAOService.findById(id);
if (user == null) {
throw new ResourceNotFoundException("id-" + id);
}
EntityModel<User> model = new EntityModel<>(user);
WebMvcLinkBuilder linkTo = linkTo(methodOn(this.getClass()).users());
model.add(linkTo.withRel("all-users"));
//how do I combine EntityModel with filtering?
return model;
}
Question 2 : How do I combine EntityModel with MappingJacksonValue?
Note : I am aware of #JsonIgnore annotation but that would apply the filter for all end points that use the domain; however, I want to restrict the filtering only to the two endpoints above.
Turns out for this to work, I have to add the #JsonFilter annotation above the DTO and provide the same name that was used while creating the SimpleFilterProvider.
#JsonFilter("UserBirthDateFilter")
public class User {
private Integer id;
#Size(min=2, message="user name must be atleast 2 characters")
#ApiModelProperty(notes="user name must be atleast 2 characters")
private String name;
#Past
#ApiModelProperty(notes="birth date cannot be in the past")
private Date birthDate;
//other methods
}
There is an easier way to do this, on your transfer object (the class you are sending back to the client), you can simply use the #JsonIgnore annotation to make sure the field is not serialized, and therefore sent to the client. So simply add #JsonIgnore inside your User class for your birthDay field.
You can also read more here about this approach:
https://www.baeldung.com/jackson-ignore-properties-on-serialization
If you need to return a different object for different endpoints (User without birthDay in your case, only for specific) you should create separate transfer objects and use those for their respective endpoints. You can pass your original entity (User) in the constructor to those classes and copy over all fields needed.
You can use Jackson's #JsonView feature. With this, you can tell a certain request mapping to produce serialized JSON with chosen set of properties.
public class View {
interface UserDetails {}
}
public class User {
#JsonView(View.UserDetails.class)
private Long id;
#JsonView(View.UserDetails.class)
private String name;
private String birthdate;
}
Controller be like
#JsonView(View.UserDetails.class)
#GetMapping("/users")
public MappingJacksonValue users() {
....
}
For question 2, I had the exact same question as you did, and here's what I did. It seems to be working:
#GetMapping(path = "/users/{id}")
public MappingJacksonValue retrieveUser(#PathVariable int id){
User user = service.findOne(id);
if(user==null){
throw new UserNotFoundException("id-"+id);
}
//"all-users", SERVER_PATH + "/users"
EntityModel<User> resource = EntityModel.of(user);
WebMvcLinkBuilder linkTo =
linkTo(methodOn(this.getClass()).retrieveAllUsers());
resource.add(linkTo.withRel("all-users"));
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("id");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserFilter",filter);
MappingJacksonValue mapping = new MappingJacksonValue(resource);
mapping.setFilters(filters);
return mapping;
}
Response for HTTP GET localhost:8080/users/1
{
"id": 1,
"links": [
{
"rel": "all-users",
"href": "http://localhost:8080/users"
}
]}
I have created an index (house) with a type "apartments" that contains 20 documents. I uploaded the Json as a binary file into elasticsearch using postman. I have a Spring Boot project that has the following classes:
EsConfig.java - I have configured the clustername which is the default name in the application.properties file.
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.search.repository")
public class EsConfig {
#Value("${elasticsearch.clustername}")
private String EsClusterName;
#Bean
public Client esClient() throws UnknownHostException {
Settings esSettings = Settings.builder()
.put("cluster.name", EsClusterName)
.put("client.transport.sniff", true)
.put("client.transport.ignore_cluster_name", false)
.build();
TransportClient client = new PreBuiltTransportClient(esSettings)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300));
return client;
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() throws Exception{
return new ElasticsearchTemplate(esClient());
}
}
Apartments.java - This is my data model. The documents have the below fields in elasticsearch.
#Document(indexName = "house", type = "apartments")
#JsonIgnoreProperties(ignoreUnknown=true)
public class Apartments {
#Id
private String id;
#JsonProperty("Apartment_Name")
private String apartmentName;
#JsonProperty("Apartment_ID")
private String apartmentId;
#JsonProperty("Area_Name")
private String areaName;
//constructors along with getters and setters
}
ApartmentSearchRepository.java - This is an interface that extends the ElasticsearchRepository interface to perform crud operations.
public interface ApartmentSearchRepository extends ElasticsearchRepository<Apartments, String> {
List<Apartments> findByApartmentName(String apartmentName);
}
EsApartmentService.java -
#Service
public class EsApartmentService {
#Autowired
ApartmentSearchRepository apartmentSearchRepository;
public List<Apartments> getApartmentByName(String apartmentName) {
return apartmentSearchRepository.findByApartmentName(apartmentName);
}
}
ApartmentController.java - I have created an endpoint that should give back those 20 documents from elasticsearch. (Also, Apartment is a POJO in my project and Apartments is the data model.)
#Autowired
EsApartmentService esApartmentService;
#GetMapping(path = "/search",produces = "application/json")
public Set<Apartment> searchApartmentByName(
#RequestParam(value = "apartmentName", defaultValue = "") String apartmentName) throws IOException {
List<Apartment> apartments= new ArrayList<>();
esApartmentService.getApartmentByName(apartmentName).forEach(apartment-> {
apartments.add(new Apartment(apartment.getApartmentName(), apartment.getApartmentId(), apartment.getAreaName()));
});
return apartments.stream()
.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Apartment::getApartmentId))));
}
This code gives back a status of 200 but with an empty response. I tried debugging but it seems that it is unable to read those documents from elasticsearch. I went through a couple of solutions but most of them have set the document data from within the code itself.
I am unable to retrieve those documents by hitting the endpoint I specified in the controller. Can someone let me know what I could be missing out on? Thanks! :)
Edit: The screenshot below shows the query and response in Postman.
As far I know, you are able to use #JsonProperty in order to map the POJO to the query response but you're loosing the ability to use the dynamic finder methods (findBy*) of spring data. The dynamic finders generation of spring data relies on reflection and there is where the field names in your POJO become important.
Would you mind to change the field names of you POJO or in your documents to verify this? Or just define a custom query? There is also a powerfull java api where you can define more complex queries: https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.misc.filter
As mentioned above by #ibexit, I removed #JsonProperty and used the native search query builder in my service. Also, it was not taking Apartment_Name and worked when I gave apartment_Name. (seems like Elasticsearch has case issues so I gave it in Camel Case.)
My changes:
Apartments.java - Removed #JsonProperty
#Document(indexName = "house", type = "apartments")
//#JsonIgnoreProperties(ignoreUnknown=true)
public class Apartments {
#Id
private String id;
//#JsonProperty("apartment_ID")
private String apartment_ID;
//#JsonProperty("Area_Name")
private String area_Name;
//#JsonProperty("Apartment_Name")
private String apartment_Name;
}
EsApartmentService.java -
#Service
public class EsApartmentService {
#Autowired
private ElasticsearchTemplate elasticsearchTemplate;
public List<Apartments> getApartmentByName(String apartmentName) {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(org.elasticsearch.index.query.QueryBuilders
.matchQuery("apartment_Name", apartmentName)).build();
Page<Apartments> sampleEntities =
elasticsearchTemplate.queryForPage(searchQuery,Apartments.class);
return sampleEntities.getContent();
}
}
Removed ApartmentSearchRepository.java file.
These changes gave me the required response! :)