I am using Jersey as a client to parse JSON into Java objects,
The problem is that I am using a service that returns different types of responses that should be mapped to a different Java object each time, so I need a way to step into the parsing process and make an arbitrary decision to tell Jersey what is the exact type of object to parse each time.
EDIT:
For example if I have Java Classes A, B, and C and the Json Response as follows:
Data{
-list {
-0 :{Result should be mapped to A}
-1 :{Result should be mapped to B}
-2 :{Result should be mapped to C}
}
}
and the list is ArrayList (or can be ArrayList of a super class for the three classes). Now when I ask Jersey to parse this JSON response, It will find an ArrayList when handling list and dosen't know what's the exact type of the object to parse into, so it convert the data inside -0, -1, -2 to a linkedHashMap as a key/value pairs.
I use jackson in a jersey client to map json to a hashMap but the mapper will work for pojo's as well. Hope the following helps.
Get the list elements into an array/list.
Loop through and determine the correct class for each element.
Pass each list element and its respective class name to a method that handles the mapping and returns an object.
import ...
import com.fasterxml.jackson.databind.ObjectMapper;
public class RequestProcessor {
void jerseyClient(){
//rest request
WebResource resource = ...
ClientResponse responseContent = resource.accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
List list = parseJSonResponse(responseContent);
for (String jsonListElement : list){
//determine correct class for jsonListElement
//Pass jsonListElement and class name to mapper method
Object obj = mapElementToObject(jsonListElement , className);
//do something with obj
}
}
private Object mapElementToObject(String jsonString, String className){
ObjectMapper mapper = new ObjectMapper();
Object obj = mapper.readValue(jsonString, Class.forName(className);
return obj;
}
private List parseJsonResponse(responseContent){
//use regexp to replace unnecessary content in response so you have a string
//that looks like -0:{element values}-1:{element values}-2:{element values}
//then split the string on "-.*\\:" into a array/list
//return the list
}
}
Related
i have list of masters that have two fields that are name and rating and after serialization to array node i need to add one more field to each object
for example i have json
[{"masterName":"Bruce","rating":30},{"masterName":"Tom","rating":25}]
and i have list of servisec in json format that look like that
[{"masterName":"Bruce","services":["hair coloring","massage"]},{"masterName":"Tom","services":["hair coloring","haircut"]}]
i need it to looks something like that
[{"masterName":"Bruce","rating":30,"services":"hair coloring,massage"},{"masterName":"Tom","rating":25,"services":"hair coloring, haircut"}]
How can do it by using jackson?
I would approach it this way. Since you want to use Jackson.
First of all, I would extend the Master class by adding the services (which seems to be an array with Strings).
So the class would look something like this:
public class Master
{
private String masterName;
private int rating;
private List<String> services = new ArrayList<>(); // THE NEW PART
// Whatever you have else in your class
}
Then you could get your JSON array, I am supposing that it comes as a String for simplicity. Serialize this array in an array with Master objects and then you can add the services as said above.
e.g.
String yourJsonString = "[{\"masterName\":\"Bruce\",\"rating\":30},{\"masterName\":\"Tom\",\"rating\":25}]";
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
List<Master> theListOfMasters = new ArrayList<>();
// Read them and put them in an Array
Master[] mastersPlainArr = mapper.readValue(yourJsonString, Master[].class);
theListOfMasters = new ArrayList(Arrays.asList(mastersPlainArr));
// then you can get your masters and edit them..
theListOfMasters.get(0).getServices.add("A NEW SERVICE...");
// And so on...
// Then you can turn them in a JSON array again:
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(theListOfMasters);
I have following class that is POJO of JSON document:
public class ApiResponse {
private boolean success;
private List<ApiRecord> data;
}
I deserialize object using ObjectMapper class in the following way:
var apiResponse = mapper.readValue(target, ApiResponse.class);
I want to make Jackson treat every ApiRecord deserialization failure as not failure of whole deserialization process but instead just get a list that contains only valid parsed objects, so the wrong elements of 'data' field are acceptable (not appearing in POJO list) and not blocking the rest ones.
Any idea on how to do this?
Do one thing add one property to the mapper object.
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
var apiResponse = mapper.readValue(target, ApiResponse.class);
My solution to this problem was to write my own deserializer which in case of failure returns null for array's element and tag the field that needs to use this deserializer with #com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = SomeClass.class).
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
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();
Inside of my DropWizard Java 7 project I have a resource that consumes JSON (org.json.simple.JSONObject). Inside of the JSON are three keys, the first two include a list of IDs with the last key holding a list of list of Strings.
The difficulties I am running into is when I am casting the types for the values of each of these keys. From what I understand, since Java does not know the object types of the JSON values until runtime I can cast the values based on the objects I am using.
#POST
#Timed
#Consumes(MediaType.APPLICATION_JSON)
public List<TwitterUserWithSocialGraph> getTwitterRelationships(JSONObject j) {
Set<Long> idListOne = (Set<Long>) j.get("id_list_one");
Set<Long> idListTwo = (Set<Long>) j.get("id_list_two");
Set<ArrayList<String>> stringSet = (Set<ArrayList<String>>) j.get("stringSet");
When I start the server, however, I continuously receive for the id lists a collection of type ArrayList. Considering that perhaps Java can hold the ids I am passing in can be either Integer or Longs (which is true, some IDs could be stored as an Integer but I need Longs since most of them do not) I instead attempt to cast the Set with Objects.
#POST
#Timed
#Consumes(MediaType.APPLICATION_JSON)
public List<TwitterUserWithSocialGraph> getTwitterRelationships(JSONObject j) {
Set<Object> idListOne = (Set<Object>) j.get("id_list_one");
Set<Object> idListTwo = (Set<Object>) j.get("id_list_two");
Set<ArrayList<String>> stringSet = (Set<ArrayList<String>>) j.get("stringSet");
Again I still receive an ArrayList as the collection type for the ID lists. Therefore I try:
#POST
#Timed
#Consumes(MediaType.APPLICATION_JSON)
public List<TwitterUserWithSocialGraph> getTwitterRelationships(JSONObject j) {
ArrayList<Object> idListOne = (ArrayList<Object>) j.get("id_list_one");
ArrayList<Object> idListTwo = (ArrayList<Object>) j.get("id_list_two");
Set<ArrayList<String>> stringSet = (Set<ArrayList<String>>) j.get("stringSet");
Now, however, I'll have to rebuild the arraylists to store Longs and then place these Longs into a Set. Clearly I am overlooking how to properly cast my types so that at runtime the proper types I want are set. Unless, of course, there is a different implementation I should be using.
If I try:
#POST
#Timed
#Consumes(MediaType.APPLICATION_JSON)
public List<TwitterUserWithSocialGraph> getTwitterRelationships(JSONObject j) {
Gson gson = new Gson();
Set<Long> idSetOne = gson.fromJson(j.get("my_tw_ids").toString(), new TypeToken<Set<Long>>() {}.getType());
Set<Long> idSetTwo = gson.fromJson(j.get("other_tw_ids").toString(), new TypeToken<Set<Long>>() {}.getType());
Set<ArrayList<String>> stringSet = gson.fromJson(j.get("stringSet").toString(), new TypeToken<Set<ArrayList<String>>>() {}.getType());
GSON can cast the types I wish to use for the id sets but it cannot properly case the string set (a "malformed string" error is raised).
Can someone point me in the proper direction of how to best cast a set of longs as well as how to cast a set of string array lists?