We have an Enum defined with Jackson annotation, "#JsonFormat(shape = JsonFormat.Shape.OBJECT)"
e.g.
package com.test;
import com.fasterxml.jackson.annotation.JsonFormat;
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum SampleEnumName {
OPTION_1 ("NAME_1", "DESCRIPTION_1", "CATEGORY_1"),
OPTION_2 ("NAME_2", "DESCRIPTION_2", "CATEGORY_2"),
OPTION_3 ("NAME_3", "DESCRIPTION_3", "CATEGORY_3");
private String name;
private String description;
private String category;
private SampleEnumName(String name, String description, String category) {
this.name = name;
this.description = description;
this.category = category;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public String getCategory() {
return category;
}
}
When its schema is generated we see it as an object, e.g.
{"type":"object","id":"urn:jsonschema:com:test:SampleEnumName","properties":{"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"}}}
But for the same, when we check the JSON generated by swagger, the following is shown in schema:
{"type":"string","enum":["OPTION_1","OPTION_2","OPTION_3"]}
Is there a way (some swagger annotation equivalent of Jackson annotation "#JsonFormat(shape = JsonFormat.Shape.OBJECT)"), which can show Object definition of Enum?
We are using swagger 1.5.7
i got the same problem, updated to Swagger 2.9.2 and solved.
Related
I'm building a POJO class to match some requests coming my way in a REST API implemented on SpringBoot. Some of the data has to be given to me, otherwise I am not willing to even serve the request. To make sure the client gives me at least what I absolutely need, I have used Lombok's #NonNull:
#Data
public class ProductRequestBody implements Serializable
{
private String name;
private String category;
private String description;
private String abbreviation;
private String labelColor;
private Double cost;
public ProductRequestBody()
{
}
#JsonCreator
public ProductRequestBody(#NonNull #JsonProperty("name") String name,
#NonNull #JsonProperty("category") String category,
#JsonProperty("description") String description,
#NonNull #JsonProperty("cost") Double cost)
{
this.name = name;
this.category = category;
this.description = description;
this.cost = cost;
}
}
(I completely understand that handling monetary quantities as Doubles is a no-no; this is just an example.
Processing this from my controller is as easy as a listener on the /products endpoint like so:
#PostMapping(value = "/products")
public Product postProduct(#RequestBody ProductRequestBody newProduct)
{
// ...
// Serve the request appropriately
// ...
}
Now, if I receive a POST request with a null field that has not been marked as #NonNull, like the following, I can serve it without issue:
{
"name": "Some Product Name",
"category": "Some Product Category",
"cost" : 10.0,
"description": null
}
My goal, however, is to be able to handle JSON requests that simply don't even have the fields they don't care about. That is, I want to be able to serve the following as well, and I currently can't:
{
"name": "Some Product Name",
"category": "Some Product Category",
"cost" : 10.0,
// No "description" field in this payload
}
How could I go about doing this? The less code, the better, as always.
If you use spring-boot and lombok you can simplify your class to be like this:
#Data
public class ProductRequestBody implements Serializable {
#NonNull
private String name;
#NonNull
private String category;
#NonNull
private Double cost;
private String description;
private String abbreviation;
private String labelColor;
}
it will return 400 if name, category or cost will be not provided and 200 otherwise.
Spring handles serializing and deserializing json without any issue.
You should let spring handle it.
You can try the following.
#Data
public class ProductRequestBody implements Serializable {
#NonNull
private String name;
#NonNull
private String category;
private String description;
private String abbreviation;
private String labelColor;
#NonNull
private Double cost;
}
If you really want to follow the pattern of creating a constructor, then you should create a constructor with only the #NonNull fields and create getter of others (lombok handles that for you). If you want to add #JsonProperty then you need to create separate getter.
#Data
public class ProductRequestBody implements Serializable
{
private String name;
private String category;
private String description;
private String abbreviation;
private String labelColor;
private Double cost;
public ProductRequestBody()
{
}
#JsonCreator
public ProductRequestBody(#NonNull #JsonProperty("name") String name,
#NonNull #JsonProperty("category") String category,
#NonNull #JsonProperty("cost") Double cost)
{
this.name = name;
this.category = category;
this.cost = cost;
}
#JsonProperty("description")
public String getDescription() {
return description;
}
}
In my spring boot project, I noticed a strange Jackson behavior. I searched over internet, found out what to do, but haven't found out why.
UserDto:
#Setter
#Getter
#AllArgsConstructor
public class UserDto {
private String username;
private String email;
private String password;
private String name;
private String surname;
private UserStatus status;
private byte[] avatar;
private ZonedDateTime created_at;
}
Adding a new user works just fine.
TagDto:
#Setter
#Getter
#AllArgsConstructor
public class TagDto {
private String tag;
}
Trying to add a new tag ends with an error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of TagDto (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
The solution to the problem was to add zero-arg constructor to the TagDto class.
Why does Jackson require no-arg constructor for deserialization in TagDto, while working just fine with UserDto?
Used same method for adding both.
My Tag and User entities are both annotated with
#Entity
#Setter
#Getter
#NoArgsConstructor
and have all args constructors:
#Entity
#Setter
#Getter
#NoArgsConstructor
public class User extends AbstractModel {
private String username;
private String password;
private String email;
private String name;
private String surname;
private UserStatus status;
#Lob
private byte[] avatar;
#Setter(AccessLevel.NONE)
private ZonedDateTime created_at;
public User(final String username, final String password, final String email, final String name, final String surname) {
this.username = username;
this.password = password;
this.email = email;
this.name = name;
this.surname = surname;
this.created_at = ZonedDateTime.now();
}
}
#Entity
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class Tag extends AbstractModel {
private String tag;
}
#MappedSuperclass
#Getter
public abstract class AbstractModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
}
Entity generation:
#PostMapping(path = "/add")
public ResponseEntity<String> add(#Valid #RequestBody final D dto) {
this.abstractModelService.add(dto);
return new ResponseEntity<>("Success", HttpStatus.CREATED);
}
public void add(final D dto) {
//CRUD repository save method
this.modelRepositoryInterface.save(this.getModelFromDto(dto));
}
#Override
protected Tag getModelFromDto(final TagDto tagDto) {
return new Tag(tagDto.getTag());
}
#Override
protected User getModelFromDto(final UserDto userDto) {
return new User(userDto.getUsername(), userDto.getPassword(), userDto.getEmail(), userDto.getName(), userDto.getSurname());
}
Error occurs when parsing JSON
{"tag":"example"}
sent via postman localhost:8081/tag/add, returns
{
"timestamp": "2020-09-26T18:50:39.974+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/tag/add"
}
I am using Lombok v1.18.12 and Spring boot 2.3.3.RELEASE with Jackson v2.11.2.
TL;DR: Solution is at the end.
Jackson supports multiple ways of creating POJOs. The following lists the most common ways, but it likely not a complete list:
Create instance using no-arg constructor, then call setter methods to assign property values.
public class Foo {
private int id;
public int getId() { return this.id; }
#JsonProperty
public void setId(int id) { this.id = id; }
}
Specifying #JsonProperty is optional, but can be used to fine-tune the mappings, together with annotations like #JsonIgnore, #JsonAnyGetter, ...
Create instance using constructor with arguments.
public class Foo {
private int id;
#JsonCreator
public Foo(#JsonProperty("id") int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Specifying #JsonCreator for the constructor is optional, but I believe it is required if there is more than one constructor. Specifying #JsonProperty for the parameters is optional, but is required for naming the properties if the parameter names are not included in the class file (-parameters compiler option).
The parameters imply that the properties are required. Optional properties can be set using setter methods.
Create instance using factory method.
public class Foo {
private int id;
#JsonCreator
public static Foo create(#JsonProperty("id") int id) {
return new Foo(id);
}
private Foo(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Create instance from text value using String constructor.
public class Foo {
private int id;
#JsonCreator
public Foo(String str) {
this.id = Integer.parseInt(id);
}
public int getId() {
return this.id;
}
#JsonValue
public String asJsonValue() {
return Integer.toString(this.id);
}
}
This is useful when a the POJO has a simply text representation, e.g. a LocalDate is a POJO with 3 properties (year, month, dayOfMonth), but is generally best serialized as a single string (yyyy-MM-dd format). #JsonValue identifies the method to be used during serialization, and #JsonCreator identifies the constructor/factory-method to be used during deserialization.
Note: This can also be used for single-value construction using JSON values other than String, but that is very rare.
Ok, that was the background information. What is happening for the examples in the question, it that UserDto works because there is only one constructor (so #JsonCreator is not needed), and many arguments (so #JsonProperty is not needed).
However, for TagDto there is only a single-argument constructor without any annotations, so Jackson classifies that constructor as a type #4 (from my list above), not a type #2.
Which means that it is expecting the POJO to be a value-class, where the JSON for the enclosing object would be { ..., "tag": "value", ... }, not { ..., "tag": {"tag": "example"}, ... }.
To resolve the issue, you need to tell Jackson that the constructor is a property initializing constructor (#2), not a value-type constructor (#4), by specifying #JsonProperty on the constructor argument.
This means that you cannot have Lombok create the constructor for you:
#Setter
#Getter
public class TagDto {
private String tag;
public TagDto(#JsonProperty("tag") String tag) {
this.tag = tag;
}
}
In my Spring Boot rest api, I have the following class:
#Entity
#Table(name="Items")
#JsonPropertyOrder({ "itemId", "description", "viewed" })
public class Item {
#ApiModelProperty(notes="Id of the item.", required=true, value="100000")
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#JsonProperty(access=Access.READ_ONLY)
private int itemId = 0;
#ApiModelProperty(notes="Item description.", required=true, value="Item1")
#NotNull
#Size(min=1, max=256)
private String description;
private int viewed;
public int getItemId() {
return this.itemId;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public int getViewed() {
return this.viewed;
}
}
When I execute the request, the JsonPropertyOrder is respected, however, in the Swagger UI (and the Swagger doc), the properties are listed as description, itemId, viewed. I.e. alphabetical. I never turned on alphabetical sorting, so not sure why its doing that... any way to turn that off? It's doing that to all my classes which are laid out in common sense / logical order...
You can define the order in which the properties are going to be shown with ApiModelProperty#position.
Example:
class MyClass {
#ApiModelProperty(position = 0)
String myFirstProperty;
#ApiModelProperty(position = 1)
String mySecondProperty;
}
It's not the most convenient method, but I couldn't find any other way to achieve this...
I am working with a web service that stores POJOs in a MongoDB. I want to make use of Mongo's 'expireAfterSeconds' time to live feature, to clear out old documents in my collection after a certain period of time.
Initially I had an implementation that sent the date to the REST service using the following JSON:
{
"testIndex": "testIndex",
"name": "hello",
"date": "2016-05-09T11:00:39.639Z"
}
The above code created the document in the collection, and with the following annotation, deleted the document after 10 seconds.
#Indexed (expireAfterSeconds=10)
private Date date;
After implementing this code, I decided I wanted to generate the date only on the Java side, meaning the JSON is now as follows:
{
"testIndex": "testIndex",
"name": "hello"
}
Then I have a constructor in the POJO using JsonCreator from Jackson
#JsonCreator
public TTLTestVO (#JsonProperty("testIndex") String testIndex, #JsonProperty("name") String name) {
this.testIndex = testIndex;
this.createdAt = new Date();
this.name = name;
}
From reading the documentation here I believe this should flag the constructor to be used when creating a new object. The testIndex and name fields are populated as before. However with this implementation, each time I check the document in my mongo the date value is 'null'. If I change the text for one of the string values to 'hello from the constructor', the constructor appears not to be called as the initial text contained in the JSON is what is added to the database.
POJO
`
#Document(collection = "test")public class TTLTestVO {
#Id private String _id;
#Indexed
private String testIndex;
#Indexed (expireAfterSeconds=10)
private Date createdAt;
private String name;
#JsonIgnore
public TTLTestVO() {
// default
}
#JsonCreator
public TTLTestVO (#JsonProperty("testIndex") String testIndex, #JsonProperty("name") String name) {
this.testIndex = "hello from the constructor";
this.name = name;
}
public String getId() {
return _id;
}
public void setId(String _id) {
this._id = _id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTestIndex() {
return testIndex;
}
public void setTestIndex(String testIndex) {
this.testIndex = testIndex;
}
public Date getDate() {
return createdAt;
}
public void setDate(Date date) {
this.createdAt = date;
}
`
After investigating some more I discovered the issue lies with the Spring Framework implementation of #JsonCreator - I removed the imports for org.springframework.cloud.cloudfoundry.com.fasterxml.jackson.annotation and replaced them with com.fasterxml.jackson.annotation. The above implementation now functions as expected.
I have been unable to find an explanation online as to why the spring version isn't working, so if anyone has any ideas please let me/ others know
In my Android project I have two types of response where both response are identical except two keys.
Response 1
{"fullName":"William Sherlock Scott Holmes","address":"221B Baker Street, London, England, UK","downloads":642,"rating":3,"repos":["https://link1","https://link2","https://link3"]}
Response 2
{"name":"Sherlock","city":"London","downloads":642,"rating":3,"repos":["https://link1","https://link2","https://link3"]}
If you see the responses only two key names are changing fullName/name and address/city
I don't want to create one more pojo for other response. My question is: is it possible to use only one Pojo to read both responses?
public class AccountInfo {
private String name;
private String city;
//other objects
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
//other setters and getters
}
Any help will be appreciated...
You can annotate the members to accept values from two different json names using the #SerializedName annotation:
#SerializedName(value = "name", alternate = {"fullName"})
private String name;
#SerializedName(value = "city", alternate = {"address"})
private String city;
Either named element can then be placed into the members that are annotated like this.
UPDATED :
#SerializedName alternate names when deserializing is added in Version 2.4
Yes, you can totally use one POJO class for deserializing both responses. Your POJO class will contain keys from both responses.
public class Response {
private String name;
private String city;
private String fullName;
private String address;
private Integer downloads;
private Integer rating;
private List<String> repos ;
}
But when using the Response class, be careful that for first response, the name and city will be null, and for the second one, the address and fullname.
Yeah you can do that in a single POJO. Try this:
public class POJO {
#SerializedName("name")
public String name;
#SerializedName("city")
public String city;
#SerializedName("fullName")
public String fullName;
#SerializedName("address")
public String address;
#SerializedName("downloads")
public Integer downloads;
#SerializedName("rating")
public Integer rating;
#SerializedName("repos")
public List<String> repos = new ArrayList<String>();
}
While parsing you have to check values for null. For eg -
While Parsing Response 1: name and city variables will be null
While Parsing Response 2: fullname and address will be null
Note : Try checking values for null before using else you'll get nullpointerexception
Define all possible fields in your POJO Class like
public class AccountInfo {
private String name;
private String city;
private String fullname;
private String address;
}
While performing operation check for null in those feilds