I am developing a restful service with Jersey. However, I am using Swagger for documentation. My Model has a property with Type of Map. Swagger shows that this attribute as an Object (not a specific type). So how can I tell Swagger that this property is from type Map ?
public class Model {
private String name;
private Map<Integer, String> myMap;
public Model(){
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<Integer, String> getMyMap() {
return myMap;
}
public void setMyMap(Map<Integer, String> myMap) {
this.myMap = myMap;
}
}
The restful service:
#POST
#Path("/createBundle")
#Consumes({MediaType.APPLICATION_JSON})
#ApiOperation(value = "Create Bundle ",
notes = "",
response = Model.class)
public Model createBundle(Bundle bundle){
return new Model();
}
I need Swagger to show it as type of Map<Integer, String>.
Swagger shows the documentation as this image.
You can set a response type in the #ApiOperation annotation:
#ApiOperation(value = "Find thingies as Map",
notes = "Multiple thingies can be returned, <code>id</code> is the ID field",
response = java.util.Map.class)
Related
I have an endpoint in spring boot that consumes this JSON as an example:
{
"userId": 3,
"postBody": "This is the body of a post",
"postTitle": "This is the title of a post",
"created": null,
"tagList": ["tag1", "tag2", "tag3"]
}
The endpoint:
#RequestMapping(value="/newPost", method = RequestMethod.POST, produces="application/json", consumes = "application/json")
#ResponseBody
public ResponseEntity newPost(#RequestBody Map<String, Object> body) throws Exception {
I know the issue here is the Request body is being saved as a Map of objects which is fine for all the other attributes except the tagList. How can I get tagList to be an array of Strings in Java?
Thanks.
A mixutre of Ankur and Jose's answers solved this, thanks for the fast responses guys!
You should probably create a Java class which represents the input JSON and use it in the method newPost(.....). For example:-
public class UserPostInfo {
private int userId;
private String postBody;
private String postTitle;
private Date created;
private List<String> tagList;
}
Also, include the getter/setter methods in this class.
If you want to modify the behavior of JSON parsing, you can use Annotations to change field names, include only non-null values, and stuff like this.
If you don't want to use a custom POJO you could also just handle the deserialization into a Map yourself. Just have your controller accept a String and then use Jackson's ObjectMapper along with TypeReference to get a map.
#RequestMapping(value="/newPost", method = RequestMethod.POST, produces="application/json", consumes = "application/json")
#ResponseBody
public ResponseEntity newPost(#RequestBody String body) throws Exception {
ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<String,Object>> typeRef = new TypeReference<HashMap<String,Object>>() {};
HashMap<String,Object> map = mapper.readValue(body, typeRef);
}
The resulting HashMap will use an ArrayList for the tag list:
You can create a custom Java POJO for the request that uses String[] versus List<String>. Here I did it for you using the site jsonschema2pojo.
package com.stackoverflow.question;
import com.fasterxml.jackson.annotation.*;
import java.util.HashMap;
import java.util.Map;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"userId",
"postBody",
"postTitle",
"created",
"tagList"
})
public class MyRequest {
#JsonProperty("userId")
private int userId;
#JsonProperty("postBody")
private String postBody;
#JsonProperty("postTitle")
private String postTitle;
#JsonProperty("created")
private Object created;
#JsonProperty("tagList")
private String[] tagList = null;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("userId")
public int getUserId() {
return userId;
}
#JsonProperty("userId")
public void setUserId(int userId) {
this.userId = userId;
}
#JsonProperty("postBody")
public String getPostBody() {
return postBody;
}
#JsonProperty("postBody")
public void setPostBody(String postBody) {
this.postBody = postBody;
}
#JsonProperty("postTitle")
public String getPostTitle() {
return postTitle;
}
#JsonProperty("postTitle")
public void setPostTitle(String postTitle) {
this.postTitle = postTitle;
}
#JsonProperty("created")
public Object getCreated() {
return created;
}
#JsonProperty("created")
public void setCreated(Object created) {
this.created = created;
}
#JsonProperty("tagList")
public String[] getTagList() {
return tagList;
}
#JsonProperty("tagList")
public void setTagList(String[] tagList) {
this.tagList = tagList;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
application.properties file contains properties that have sub properties:
status.available=00, STATUS.ALLOWED
status.forbidden=01, STATUS.FORBIDDEN
status.authdenied=05, STATUS.AUTH_DENIED
The idea was to get those properties into the application like this:
#Configuration
#ConfigurationProperties(prefix = "status")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
public class StatusProperties {
private Map <String, List <String>> statusMapping;
public Map <String, List <String>> getStatusMapping () {
return statusMapping;
}
public void setStatusMapping (Map <String, List <String>> statusMapping) {
this.statusMapping = statusMapping;
}
}
The problem is that this Map is returned empty. I must be doing something wrong. Maybe this is not even possible in Spring to do like this?
I'm not sure about your choice regarding the data type and its assignment. I'd suggest you to rethink this design.
To your main question:
Spring can't know, that status.* should be mapped to private Map <String, List <String>> statusMapping;. Also as your class is named *properties, It seems that you don't want it to be a #Configuration class. Consider the following pattern:
First, create a properties class to hold the properties:
#ConfigurationProperties(prefix = "status")
public class StatusProperties {
private Map.Entry<Integer, String> available;
private Map.Entry<Integer, String> forbidden;
private Map.Entry<Integer, String> authdenied;
public Map.Entry<Integer, String> getAvailable() {
return available;
}
public void setAvailable(Map.Entry<Integer, String> available) {
this.available = available;
}
public Map.Entry<Integer, String> getForbidden() {
return forbidden;
}
public void setForbidden(Map.Entry<Integer, String> forbidden) {
this.forbidden = forbidden;
}
public Map.Entry<Integer, String> getAuthdenied() {
return authdenied;
}
public void setAuthdenied(Map.Entry<Integer, String> authdenied) {
this.authdenied = authdenied;
}
}
Now, your IDE should be able to read the docs from the setters while editing application.properties and check the validity. Spring can autowire the fields and automatically create the correct data types for you.
Consider mapping the Entries to a Map (Or, as I already told, change the design)
Now, you can use this properties class in your configuration:
#Configuration
#EnableConfigurationProperties(StatusProperties.class)
public class StatusConfiguration {
#Bean
public MyBean myBean(StatusProperties properties) {
return new MyBean(properties);
}
}
I found the solution:
application.properties:
app.statuses[0].id=00
app.statuses[0].title=STATUS.ALLOWED
app.statuses[1].id=01
app.statuses[1].title=STATUS.FORBIDDEN
app.statuses[2].id=02
app.statuses[2].title=STATUS.CONTRACT_ENDED
Properties.java
#Component
#ConfigurationProperties(prefix = "app")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
public class StatusProperties {
private List<Status> statuses = new ArrayList<>();
public List <Status> getStatuses () {
return statuses;
}
public void setStatuses (List <Status> statuses) {
this.statuses = statuses;
}
public static class Status {
private String id;
private String title;
public String getId () {
return id;
}
public void setId (String id) {
this.id = id;
}
public String getTitle () {
return title;
}
public void setTitle (String title) {
this.title = title;
}
}
}
I'm using Jackson in a java Rest Api to handle request params.
My Bean class :
public class ZoneModifBeanParam extends ModifBeanParam<Zone> {
#FormParam("type")
private String type;
#FormParam("geometry")
private Geometry geometry;
#FormParam("name")
private String name;
...
My API interface :
#POST
#Consumes("application/json")
#Produces("application/json; subtype=geojson")
#ApiOperation(value = "Create a zone", notes = "To create a zone")
public Response createZone(ZoneModifBeanParam zoneParam) {
...
This Works fine but I need to receive other params that aren't specified by my Bean in a Map.
Example :
{
"geometry": {...},
"name": "A circle name",
"type": "4",
"hello": true
}
By receiving this I need to store in a Map (named unrecognizedFields and declared in my bean) the couple ("hello", true).
Is there any annotation or object allowing this?
Just use #JsonAnySetter. That's what it's made for. Here is a test case
public class JacksonTest {
public static class Bean {
private String name;
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
private Map<String, Object> unrecognizedFields = new HashMap<>();
#JsonAnyGetter
public Map<String, Object> getUnrecognizedFields() {
return this.unrecognizedFields;
}
#JsonAnySetter
public void setUnrecognizedFields(String key, Object value) {
this.unrecognizedFields.put(key, value);
}
}
private final String json
= "{\"name\":\"paul\",\"age\":600,\"nickname\":\"peeskillet\"}";
private final ObjectMapper mapper = new ObjectMapper();
#Test
public void testDeserialization() throws Exception {
final Bean bean = mapper.readValue(json, Bean.class);
final Map<String, Object> unrecognizedFields = bean.getUnrecognizedFields();
assertEquals("paul", bean.getName());
assertEquals(600, unrecognizedFields.get("age"));
assertEquals("peeskillet", unrecognizedFields.get("nickname"));
}
}
The #JsonAnyGetter is used on the serialization side. When you serialize the bean, you will not see the unrecognizedFields in the JSON. Instead all the properties in the map will be serialized as top level properties in the JSON.
You may be able to ignore the unrecognized fields safely by configuring the ObjectMapper, however to specifically put them as key-value pairs of a Map field, you'll need your own de-serializer.
Here's a (heavily simplified) example:
Given your POJO...
#JsonDeserialize(using=MyDeserializer.class)
class Foo {
// no encapsulation for simplicity
public String name;
public int value;
public Map<Object, Object> unrecognized;
}
... and your custom de-serializer...
class MyDeserializer extends JsonDeserializer<Foo> {
#Override
public Foo deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// new return object
Foo foo = new Foo();
// setting unrecognized container
Map<Object, Object> unrecognized = new HashMap<>();
foo.unrecognized = unrecognized;
// initializing parsing from root node
JsonNode node = p.getCodec().readTree(p);
// iterating node fields
Iterator<Entry<String, JsonNode>> it = node.fields();
while (it.hasNext()) {
Entry<String, JsonNode> child = it.next();
// assigning known fields
switch (child.getKey()) {
case "name": {
foo.name = child.getValue().asText();
break;
}
case "value": {
foo.value = child.getValue().asInt();
break;
}
// assigning unknown fields to map
default: {
foo.unrecognized.put(child.getKey(), child.getValue());
}
}
}
return foo;
}
}
Then, somewhere...
ObjectMapper om = new ObjectMapper();
Foo foo = om.readValue("{\"name\":\"foo\",\"value\":42,\"blah\":true}", Foo.class);
System.out.println(foo.unrecognized);
Output
{blah=true}
I am using Jersey and face an issue about how to parse a complex object to xml format, please help me about it, many thanks.
Here is the detail.
First, I make a entity container object like below:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class RestResponse {
//It can be any kinds of type, collection, single object etc
private Object data;
//... still have many properties
public RestResponse() {
}
public RestResponse(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
And here is one of my entity class:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Entity1{
private String name;
private Map<String, Object> otherData = new HashMap<String, Object>();
public Entity1(){
this.name = "aaa";
otherData.put("address", "XXXXX");
otherData.put("age", 13);
//more...
this.otherData = otherData
}
public Entity1(String name, Integer age){
this.name = "aaa";
otherData.put("address", "XXXXX");
otherData.put("age", age);
this.otherData = otherData
}
public String getName() {
return name;
}
public Map<String, Object> getOtherData() {
return otherData;
}
}
Here is my resource class:
#Path("/test")
public class EntityResource{
#GET
#Path("test1")
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response test1() {
Entity1 entity = new Entity1();
return Response.ok(new RestResponse(entity)).build();
}
#GET
#Path("test2")
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response test2() {
List entities = new ArrayList<Entity1>();
entities.add(new Entity1("E1"));
entities.add(new Entity1("E2"));
return Response.ok(new RestResponse(entities)).build();
}
}
Configure jersey with above code, it works fine when I require json format response, but for xml format response, I always get 500 error, am I missing something?
After some research, I found the solution, the answer is simple, I register JacksonXMLProvider.class as a XML provider, hope this can help other people.
I am trying to unmarshall a json file in a way such that few properties of Json are mapped into a HashMap that is present in my model class.Rest of the properties are mapped to the respective fields of the class.Please find the Json below:
{
"_id":2,
"Name":"xyz",
"Age":20,
"MEMO_TEXT":"yyy",
"MEMO_LINK":"zzz",
"MEMO_DOB":"",
"MEMO_USERNAME":"linie orange",
"MEMO_CATEGORY":2,
"MEMO_UID":"B82071415B07495F9DD02C152E4805EC"
}
And here is the Model class to which I want to map this Json:
public class Model{
private int _id;
private String name;
private int age
private HashMap<String, String> columns;
//Getters and Setter methods
}
So here, what i want is to get a map columns that contains keys "MEMO_TEXT","MEMO_LINK","MEMO_DOB","MEMO_USERNAME","MEMO_CATEGORY","MEMO_UID"
and rest of the properties in Json are mapped to their respective fields.
Is it possible to do this using ObjectMapper of Jackson Library?
You can use #JsonAnySetter to annotate a method to be called for "other" properties:
#Test
public void partial_binding() throws Exception {
Model model = mapper.readValue(Resources.getResource("partial_binding.json"), Model.class);
assertThat(model.name, equalTo("xyz"));
assertThat(model.columns, hasEntry("MEMO_TEXT", "yyy"));
assertThat(
mapper.writeValueAsString(model),
json(jsonObject()
.withProperty("Name", "xyz")
.withProperty("MEMO_TEXT", "yyy")
.withAnyOtherProperties()));
}
public static class Model {
#JsonProperty
private int _id;
#JsonProperty("Name")
private String name;
#JsonProperty("Age")
private int age;
private HashMap<String, String> columns;
#JsonAnyGetter
public HashMap<String, String> getColumns() {
return columns;
}
public void setColumns(HashMap<String, String> columns) {
this.columns = columns;
}
#JsonAnySetter
public void putColumn(String key, String value) {
if (columns == null) columns = new HashMap<>();
columns.put(key, value);
}
}
Also, #JsonAnyGetter does "kind of the reverse", so this should serialize and deserialize the same way.
One of several ways to achieve what you want is to add a constructor:
#JsonCreator
public Model(Map<String, Object> fields) {
this._id = (int) fields.remove("_id");
this.name = (String) fields.remove("Name");
this.age = (int) fields.remove("Age");
this.columns = new HashMap<String, String>();
for (Entry<String, Object> column : fields.entrySet()) {
columns.put(column.getKey(), column.getValue().toString());
}
}
Be aware that if you serialize it back to JSON the structure will be diffrent than the initial one.
Try using a SerializerProvider. A SerializerProvider can modify the deserialization, enabling custom deserialization.