I am trying to receive a JSON object in #RequestBody which is not known to me i.e. the JSON Object could be of any length and data.
Let's say, JSON could be as shown below
{'seqNo': 10 }
{'country': 'US', 'state': 'CA'}
{'customer': 'Alex', product: 'UPS', date:'25-Mar-2018'}
And In Spring Boot Api, I have a method to receive that JSON Object.
#PostMapping(value = "/lookup")
public ResponseEntity<AppResponse> getLookup(#RequestBody LookupRequestObject lookupRequestObject) {
// THIS METHOD KNOWS WHICH FIELD TO USE
// FURTHER LOGIC WOULD BE HERE.
return ResponseEntity.ok(response);
}
I have read about Jackson Serialization but still finding solution for this.
Customize the Jackson ObjectMapper
Any help would be much appreciated.
You could just use a map for your input.
Then you can access filed in the map depending on what kind of fields it contains.
#PostMapping(value = "/lookup")
public ResponseEntity<AppResponse> getLookup(#RequestBody Map<String, Object> lookupRequestObject) {
// THIS METHOD KNOWS WHICH FIELD TO USE
// FURTHER LOGIC WOULD BE HERE.
return ResponseEntity.ok(response);
}
If JSON object structure is not known, you can always use Map<String, Object> type and convert it to POJO or directly work on Map. In case you need POJO you can use convertValue method:
#PostMapping(value = "/lookup")
public ResponseEntity<AppResponse> getLookup(#RequestBody Map<String, Object> payload) {
// read map
ObjectMapper objectMapper = new ObjectMapper();
if (payload.containsKey("seqNo")) {
Sequence seq = objectMapper.convertValue(payload, Sequence.class);
// other logic
} else if (payload.containsKey("country")) {
Country country = objectMapper.convertValue(payload, Country.class);
}
// the same for other types
// FURTHER LOGIC WOULD BE HERE.
return ResponseEntity.ok(response);
}
You can also try with deserialising to com.fasterxml.jackson.databind.JsonNode but it binds controller with Jackson which is not good from other side.
The answer by #Patrick Adler is absolutely correct. You can use Map as your parameter of the method. Two important additions: Map corresponds to JSON Object so when a JSON object is passed to your method Spring (using Jackson by default) will convert it to map, so no additional code needed. Also to be sure you can add to your annotation that you expect to receive JSON input:
So change the line
#PostMapping(value = "/lookup") to
#PostMapping(value = "/lookup", headers = "Accept=application/json") And finally the input that you posted is not a valid single JSON Object. It is 3 separate JSON Objects. So either you expect a JSON array containing JSON Objects or a Single JSON Object. If you expect a JSON Array then instead of Map<String, Object> parameter in your method use List<Map<String, Object>> so your solution should look either
#PostMapping(value = "/lookup", headers = "Accept=application/json")
public ResponseEntity<AppResponse> getLookup(#RequestBody Map<String, Object> lookupRequestObject) {
// THIS METHOD KNOWS WHICH FIELD TO USE
// FURTHER LOGIC WOULD BE HERE.
return ResponseEntity.ok(response);
}
or the same but with List<Map<String, Object>> param instead of just map
Related
I'm currently calling this API https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/gbp.json
This has the path param of gbp which then is included in the response
{
"date": "2021-09-14",
"gbp": {
// omitted for brevity
}
}
Im using the RestTemplate.getForObject method to make the GET HTTP request which is successful however i am not sure how i would go about typing the response.
I will be calling this url with multiple different path parameters. So for example https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/eur.json is valid which will result in a response of
{
"date": "2021-09-14",
"eur": {
// omitted for brevity
}
}
So its not as easy as typing a gbp property on the response as Map<String, Double> etc. And i dont want to create a different class for each possible response.
So my question basically is. How can i type this? I've tried to use a custom #JsonDeserialzer annotation on a class which represents the data however since it does not know the key that was a bit of a dead end.
Is the only way to achieve this by using a custom ObjectMapper where i can pass the key to a customer deserializer rather than using the annotation?
First, consider using WebClient instead of RestTemplate, if you're on spring framework 5 (or switch to the webflux stack).
You don't need a custom ObjectMapper, but can just loop the properties by using restTemplate.getForEntity, like:
ResponseEntity<String> response = restTemplate.getForEntity
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(response.getBody());
Iterator<Map.Entry<String, JsonNode>> it = root.fields();
while (it.hasNext()) {
Map.Entry<String, JsonNode> field = it.next();
System.out.println("key: " + field.getKey());
//Add some logic here ;)
//Probably you want the second property, if it exists ...
if (it.hasNext) {
field = it.next();
JsonNode currencyNode = field.getValue();
}
}
If you need or want an object, try the #JsonAnySetter - see fe. here Unstable behavior with Jackson Json and #JsonAnySetter
I'm writing logic for framing the input request body which needs to be sent when calling the Rest API. I'm using Map for doing it so and then using object mapper i'm converting into string which will be of json format.
Example: Sample input request body
{ "A":{"1":"aa","2":"bb" },"B":{"3":"cc","4":"dd"}}
My code will look like this
MyReq req=new MyReq();
Map<String, String> A = Maps.newHashMap();
A.put("1","aa");
A.put("2","bb");
Map<String, String> B = Maps.newHashMap();
B.put("3","cc");
B.put("4","dd");
req.setA(A);
req.setB(B);
final ObjectMapper obj = new ObjectMapper();
String myjson=obj.writeValueAsString(req);
But , in case of this format, how can i do it,
{"A":{"1":"aa","2":"bb"},"B":{"New":{"new1":"qq","new2","zz",},"3":"cc","4":"dd"}}
The maps you are using and the base object of the response represent simple JSON objects (as opposed to arrays, ...). You have many choices to create the response you are describing. To expand your example, in JAXB you could do the following:
#XMLRootElement
public class MyReq {
....
#XmlElement(name = "3")
private String three;
But do not do this in the case of non descriptive properties such as 3 Use JAXB if the response is clearly defined and used frequently and/or if the JAXB classes are used in other parts of the application (JPA beans, ...).
You can also replace the class MyRec using a Map<String,Object> and simply put the other maps in as well as the other values put("3","cc").
Ok take also a look at the JSON-P API which is the best solution for such a random example:
JsonObject response = Json.createObjectBuilder()
.add("A", Json.createObjectBuilder().add("1", "aa").add("2", "bb"))
.add("B", Json.createObjectBuilder().add("NEW", Json.createObjectBuilder().add("new1", "qq").add("new2", "zz")))
.add("3", "cc")
.add("4", "dd").build();
I'm quite new to JSON, and I've looked around trying to work out what to do but not sure if I fully understand. I am making an external API call returning:
2015-12-21 01:22:09 INFO RiotURLSender:60 - Total json:
{"USERNAME":{"profileIconId":984,"revisionDate":1450655430000,"name":"USERNAME2","id":38584682,"summonerLevel":30}}
Where 'USERNAME' (And USERNAME2 - which can be very slightly different to USERNAME) will vary depending on what you pass the call's parameters. I was using Jackson Object Mapper to map the individual values within the USERNAME object - but didn't realise I had to map the object as well.
I've been using annotations in the DTOs like:
#JsonProperty("profileIconId")
private Long profileIconId;
and mapping using:
summonerRankedInfoDTO = mapper.readValue(jsonString, SummonerRankedInfoDTO.class);
How do I map using a value of USERNAME which is changing every single time?
Also this seems a bit odd, is this bad practice to have the actual varying key rather than just have the same key and different value?
Thanks
you can use following mentioned annotation #JsonAnyGetter And #JsonAnySetter.
Add this code into ur domain class. So any non-mapped attribute will get populated into "nonMappedAttributes" map while serializing and deserializing the Object.
#JsonIgnore
protected Map<String, Object> nonMappedAttributes;
#JsonAnyGetter
public Map<String, Object> getNonMappedAttributes() {
return nonMappedAttributes;
}
#JsonAnySetter
public void setNonMappedAttributes(String key, Object value) {
if (nonMappedAttributes == null) {
nonMappedAttributes = new HashMap<String, Object>();
}
if (key != null) {
if (value != null) {
nonMappedAttributes.put(key, value);
} else {
nonMappedAttributes.remove(key);
}
}
}
You should try to keep the keys the exact same if possible and change values, otherwise you'll have to change your JSON. Since JSON returns a value from the key, the value can change to anything it wants, but you'll be able to return it from the key. This doesn't work the other way around though.
Anyway to your question, you may have a little better luck using something like the GSON library, its pretty simple to use.
You can create the instance and pass it the JSON string:
Gson gson = new Gson();
JsonObject obj = gson.fromJson(JSON_DOCUMENT, JsonObject.class);
Then you can get certain elements from that now parsed JSON object.
For example, in your JSON string, username returns another JSON element, so you can do:
JsonObject username = obj.get("USERNAME").getAsJsonObject();
Then just repeat the same steps from there to get whatever value you need.
So to get the name which returns "USERNAME2":
username.get("name").getAsString();
Coming together with:
JsonObject obj = gson.fromJson(JSON_DOCUMENT, JsonObject.class);
JsonObject username = obj.get("USERNAME").getAsJsonObject();
username.get("name").getAsString();
In my application I have lot of overviews (tables) with sorting and filtering capabilities. And becuase the different column can hold different value type (strings, numbers, dates, sets, etc.) the filter for these columns also can bring different values. Let me show you few examples (converted to JSON already as is sent to server via REST request):
For simple string value it is like:
{"<column_name>":"<value>"}
For number and date column the filter looks like:
{"<column_name>":[{"operator":"eq","value":"<value>"}]}
{"<column_name>":[{"operator":"eq","value":"<value1>"},{"operator":"gt","value":"<value2>"}]}
For set the filter looks like
{"<column_name>":["<value1>","<value2>"(,...)]}
Now I need to parse that JSON within a helper class that will build the WHERE clause of SQL query. In PHP this is not a problem as I can call json_decode and then simply check whether some value is array, string or whatever else... But how to do this simply in Java?
So far I am using Spring's JsonJsonParser (I didn't find any visible difference between different parsers coming with Spring like Jackson, Gson and others).
I was thinking about creating an own data object class with three different constructors or having three data object classes for all of the three possibilities, but yet I have no clue how to deal with the value returned for column_name after the JSON is parsed by parser...
Simply looking on the examples it gives me three possibilities:
Map<String, String>
Map<String, Map<String, String>>
Map<String, String[]>
Any idea or clue?
Jackson's ObjectMapper treeToValue should be able to help you.
http://fasterxml.github.io/jackson-databind/javadoc/2.2.0/com/fasterxml/jackson/databind/ObjectMapper.html#treeToValue%28com.fasterxml.jackson.core.TreeNode,%20java.lang.Class%29
Your main problem is that the first version of you JSON is not the same construction than the two others. Picking the two others you could deserialize your JSON into a Map<String, Map<String, String> as you said but the first version fits a Map.
There are a couple solutions available to you :
You change the JSON format to always match the Map<String, Map<String, String> pattern
You first parse the JSON into a JsonNode, check the type of the value and deserialize the whole thing into the proper Map pattern.
(quick and dirty) You don't change the JSON, but you try with one of the Map patterns, catch JsonProcessingException, then retry with the other Map pattern
You'll have to check the type of the values in runtime. You can work with a Map<String, Object> or with JsonNode.
Map<String, Object>
JsonParser parser = JsonParserFactory.getJsonParser();
Map<String, Object> map = parser.parseMap(str);
Object filterValue = filter.get("<column_name>");
if (filterValue instanceof String) {
// str is like "{\"<column_name>\":\"<value>\"}"
} else if (filterValue instanceof Collection) {
for (Object arrayValue : (Collection<Object>) filterValue) {
if (arrayValue instanceof String) {
// str is like "{\"<column_name>\":[\"<value1>\",\"<value2>\"]}"
} else if (arrayValue instanceof Map) {
// str is like "{\"<column_name>\":[{\"operator\":\"eq\",\"value\":\"<value>\"}]}"
}
}
}
JsonNode
ObjectMapper mapper = new ObjectMapper();
JsonNode filter = mapper.readTree(str);
JsonNode filterValue = filter.get("<column_name>");
if (filterValue.isTextual()) {
// str is like "{\"<column_name>\":\"<value>\"}"
} else if (filterValue.isArray()) {
for (JsonNode arrayValue : filterValue.elements()) {
if (arrayValue.isTextual()) {
// str is like "{\"<column_name>\":[\"<value1>\",\"<value2>\"]}"
} else if (arrayValue.isObject()) {
// str is like "{\"<column_name>\":[{\"operator\":\"eq\",\"value\":\"<value>\"}]}"
}
}
}
I' m developing an Android REST client. We use JSON as data exchange format, so I use a Jackson parser. I get different Json responses from the server like simple arrays:
{"user_id":"332","user_role":"1"}
or something else. All these stuff I parse to LinkedHashMap<String, Object> and everything works perfectly but when I got this response from the server:
[ { "user_id":"352",
"user_role":"expert",
"name":"Test 12-18",
"description":"Test" },
{ "user_id":"263",
"user_role":"novice lab",
"name":"Tom's Desk",
"description":"Desk"}
]
I got null: {} after parsing.Here is my code where i use Jackson:
ObjectMapper mapParametersToJSON = new ObjectMapper();
String serverResponseBody = responseFromServer.getBody();
LinkedHashMap<String, Object> resultofOperation = new LinkedHashMap<String,
Object>();
TypeReference<LinkedHashMap<String,Object>> genericTypeReferenceInformation = new
TypeReference<LinkedHashMap<String,Object>>() {};
try {
resultofOperation = mapParametersToJSON.readValue(serverResponseBody,
genericTypeReferenceInformation);
So, why Jackson failed to parse this? How can I fix this?
Others have suggested the problem, but solutions are bit incomplete. If you need to deal with JSON Objects and Arrays, you can either bind to java.lang.Object, check the type:
Object stuff = objectMapper.readValue(json, Object.class);
and you will get either List or Map (specifically, ArrayList or LinkedHashMap, by default; these defaults can be changed).
Or you can do JSON trees with JsonNode:
JsonNode root = objectMapper.readTree(json);
if (root.isObject()) { // JSON Object
} else if (root.isArray()) { ...
}
latter is often more convenient.
One nice thing is that you can still create regular POJOs out of these, for example:
if (root.isObject()) {
MyObject ob = objectMapper.treeToValue(MyObject.class);
}
// or with Object, use objectMapper.convertValue(ob, MyObject.class)
so you can even have different handling for different types; go back and forth different representations.
The first JSON in your question is a map, or an object. The second is an array. You're not parsing an array, you're parsing a map.
You need to do something like this:
List<MyClass> myObjects = mapper.readValue(jsonInput, new TypeReference<List<MyClass>>(){});
Almost identical question with answer here.
In JSON the {"key": "value"} is Object and the ["this", "that"] is Array.
So, in case when you're receiving the array of objects you should use something like List<Map<Key, Value>>.
You are facing an error, because [] construction can't be translated into Map reference, only in List or array.
I would recommend do it something in this way:
ObjectMapper objectMapper = new ObjectMapper();
List<Map<String,String>> parsedResult = objectMapper.reader(CollectionType.construct(LinkedList.class, MapType.construct(LinkedHashMap.class, SimpleType.construct(String.class), SimpleType.construct(String.class)))).readValue(serverResponseBody);
//if you need the one result map
Map<String, String> resultMap = new LinkedHashMap<String, String>();
for (Map<String, String> map: parsedResult){
resultMap.putAll(map);
}