Spring RestTemplate, GsonHttpMessageConverter - java

given JSON
{"news" : [ {...}, {...}, {...} ] }
the array contains custom objects, which I already declared as POJOs. Want I simply want to map my custom objects by keypath "news".
restTemplate.exchange(URI + "/news/{limit}/", HttpMethod.GET, CustomObject[].class, 10)
throws an exception, because this JSON is expected
[ {...}, {...}, {...}]
Is there a way to configure RestTemplate to match my needs?
Regards
Update:
restTemplate.exchange(URI + "/news/{limit}/", HttpMethod.GET, requestEntity, JsonElement.class,10).getBody().getAsJsonObject().get("news");
CustomObject[] result = gson.fromJson(body, CustomObject[].class);
This snippet works but is there a cleaner way? For my suprise mapping to JSONObject doesn't even worked, JSONElement did the job at the end.

Well I'll add my update in here to close this question. If someone can provide a cleaner solution I'll be happy to accept it:
restTemplate.exchange(URI + "/news/{limit}/", HttpMethod.GET, requestEntity, JsonElement.class,10).getBody().getAsJsonObject().get("news");
CustomObject[] result = gson.fromJson(body, CustomObject[].class);
This snippet works but is there a cleaner way? For my suprise mapping to JSONObject doesn't even worked, JSONElement did the job at the end.

Related

Java get nested value from ResponseEntity without creating a pojo

I am trying to get a single nested value from a ResponseEntity but I am trying to do so without having to create a pojo for every possible item as this is a third party api response.
Example response.getBody() as it appears in Postman:
{
"message": "2 records found",
"records": [
{
"Account": {
"Id": "1",
"Name": "Foo Inc"
},
"CaseNumber": "200",
"Contact": {
"FirstName": "Foo",
"LastName": "Bar"
},
"Status": "In Progress",
"StatusMessage": "We are working on this."
},
{
"Account": {
"Id": "1",
"Name": "Foo Inc"
},
"CaseNumber": "100",
"Contact": {
"FirstName": "Foo",
"LastName": "Bar"
},
"Status": "Closed"
}
]
}
Basically, if I were in JS, I am looking for:
for(let record of res.body.records){
if(record && record.CaseNumber === "200"){
console.log(record.Status)
}
res.body.records[0].Status
Currently, they are are doing this to check if the response is empty:
ResponseEntity<Object> response = restTemplate.exchange(sfdcURL, HttpMethod.POST, entity, Object.class);
LinkedHashMap<Object, Object> resMap = (LinkedHashMap<Object, Object>) response.getBody();
List<Object> recordsList = (List<Object>) resMap.get("records");
if (recordsList.size() <= 0) { return error }
But I need to get the value of of "Status" and I need to do so without creating a pojo.
I appreciate any guidance on how I can do this in Java
UPDATE
So the response.getBody() is returned and when it is displayed in Postman, it looks like the pretty JSON shown above. However, when I do:
System.out.println(response.getBody().toString())
it looks like:
{message=2 Records Found, records=[{Account={Id=1, Name=Foo Inc}, CaseNumber=200, Contact={FirstName=Foo, LastName=Bar}, //etc
To make it worse, one of the fields appears in the console as follows (including linebreaks):
[...], Status=In Progress, LastEmail=From: noreply#blah.com
Sent: 2022-08-08 10:14:54
To: foo#bar.com
Subject: apropos case #200
Hello Foo,
We are working on your case and stuff
Thank you,
us, StatusMessage=We are working on this., OtherFields=blah, [...]
text.replaceAll("=", ":") would help some, but won't add quotations marks nor would it help separate that email block.
How can I so that the responses here like ObjectMapper and JSONObject can work?
You can either convert the string to valid json (not that trivial) and deserialise into a Map<String, Object>, or just pluck the value out of the raw string using regex:
String statusOfCaseNumber200 = response.getBody().toString()
.replaceAll(".*CaseNumber=200\\b.*?\\bStatus=([^,}]*).*", "$1");
This matches the whole string, captures the desired status value then replaces with the status, effectively "extracting" it.
The regex:
.*CaseNumber=200\b everything up to and including CaseNumber=200 (not matching longer numbers like 2001)
.*? as few chars as possible
\\bStatus= "Status=" without any preceding word chars
([^,}]*) non comma/curly brace characters
.* the rest
It's not bulletproof, but it will probably work for your use case so it doesn't need to be bulletproof.
Some test code:
String body = "{message=2 Records Found, records=[{Account={Id=1, Name=Foo Inc}, CaseNumber=200, Contact={FirstName=Foo, LastName=Bar}, Status=In Progress, StatusMessage=We are working on this.}, {Account={Id=1, Name=Foo Inc}, CaseNumber=100, Contact={FirstName=Foo, LastName=Bar}, Status=Closed}]";
String statusOfCaseNumber200 = body.replaceAll(".*CaseNumber=200\\b.*?\\bStatus=([^,}]*).*", "$1");
System.out.println(statusOfCaseNumber200); // "In Progress"
PLEASE DO NOT use Genson as Hiran showed in his example. The library hasn't been updated since 2019 and has many vulnerable dependencies!
Use Jackson or Gson.
Here how you can serialize a string into a Jackson JsonNode:
ObjectMapper mapper = new ObjectMapper();
String json = ...;
JsonNode node = mapper.readTree(json);
If you want to serialize a JSON object string into a Map:
ObjectMapper mapper = new ObjectMapper();
String json = ...;
Map<String, Object> map = mapper.readValue(json, HashMap.class);
You can read more about JsonNode here and a tutorial here.
You can use JSON-Java library and your code will look like this:
JSONObject jsonObject = new JSONObject(JSON_STRING);
String status = jsonObject.getJSONArray("records")
.getJSONObject(0)
.getString("Status");
System.out.println(status);
Or in a loop
JSONArray jsonArray = new JSONObject(jsonString).getJSONArray("records");
for(int i =0; i < jsonArray.length(); i++) {
String status = jsonArray
.getJSONObject(i)
.getString("Status");
System.out.println(status);
}
So the response.getBody() is returned and when it is displayed in Postman, it looks like the pretty JSON shown above. However, when I do:
...
text.replaceAll("=", ":") would help some, but won't add quotations
marks nor would it help separate that email block.
How can I so that the responses here like ObjectMapper and JSONObject
can work?
Firstly, Jackson is the default message converter which Spring Web uses under the hood to serialize and deserialize JSON. You don't need to introduce any dependencies.
Secondly, the process serialization/deserialization is handled by the framework automatically, so that in many cases you don't need to deal with the ObjectMapper yourself.
To emphasize, I'll repeat: in most of the cases in Spring you don't need to handle raw JSON yourself. And in the body of ResponseEntiry<Object> produced by the method RestTemplate.exchange() you have a LinkedHashMap in the guise of Object, it's not a raw JSON (if you want to know why it is a LinkedHashMap, well because that's how Jackson stores information, and it's a subclass of Object like any other class in Java). And sure, when you're invoking toString() on any implementation of the Map you'll get = between a Key and a Value.
So, the problem you've mentioned in the updated question is artificial.
If you want to deal with a Map instead of an object with properly typed properties and here's how you can do that:
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<LinkedHashMap<String, Object>> response = restTemplate.exchange(
sfdcURL, HttpMethod.POST, entity, new ParameterizedTypeReference<>() {}
);
Map<String, Object> resMap = response.getBody();
List<Object> recordsList = (List<Object>) resMap.get("records");
if (recordsList.isEmpty()) { ... }
If there are redundant lines in the Values which you want to trim, then as a remedy you can introduce a custom Jackson-module declaring a Deserializer which would handle leading/trailing white-space and new lines, described in this answer. Deserialize in the module would be applied by default, other options would require creating classes representing domain objects which you for some reasons want to avoid.
As Oliver suggested JsonNode seems to be the best approach. But, if I receive the ResponseEntity<Object>, I still cannot figure out a way to convert it to readable Json (and thus convert it to JsonNode), so I am still open to responses for that part.
I was able to get it to work by changing the ResponseEntity<Object> to ResponseEntity<JsonNode> so this is what I will be submitting for now:
ResponseEntity<JsonNode> response = restTemplate.exchange(sfdcURL,
HttpMethod.POST, entity, JsonNode.class);
JsonNode records = response.getBody().get("records");
String status = null;
String statusMessage = null;
for (JsonNode rec : records) {
if(rec.get("CaseNumber").asText().equals(caseNumber)) {
status = rec.get("Status").asText();
if(rec.has("StatusMessage")) {
statusMessage = rec.get("StatusMessage").asText();
}
} else {
statusMessage = "Invalid CaseNumber";
}
}
Because the overall method returns a ResponseEntity<Object> I then converted my strings to a HashMap and returned that:
HashMap<String, String> resMap = new HashMap<String, String>();
resMap.put("Status", status);
resMap.put("StatusMessage", statusMessage);
return new ResponseEntity<>(resMap, HttpStatus.OK);
This is not a perfect solution, but it works for now. Would still be better for exception handling if I could receive a ResponseEntity<Object> and then convert it to a JsonNode though. Thanks everyone for the responses!

How to fix 422 status response from patch

I have a react front and java backend. I am using a axios.patch request and I keep receiving a 422 response. I do not know what is wrong. I would like this to update correctly. I'm using jsonpatch to try and update this object.
I've tried numerous things. Is there something wrong with my request? I do not know.
Here my code:
return API.patch(`bmwsales/updateWeb/${vid}/`, veh, {headers: {
'Authorization' : basic }})
.then((response) =>{
if (response.status==200){
dispatch(updateVehicleSuccess());
}
}, (error) =>{
if (error.response.status == 500){
dispatch(vehicleError(error.message, "Could not update rfidtag, please try again."));
}else if
(error.response.status == 422){
dispatch(vehicleError(error.message, "Could not update rfidtag, please try again."));
}
controller:
#PatchMapping("/bmwsales/updateWeb/{id}")
public ResponseEntity<?> updateVehicleTagWeb(#PathVariable(value="id") Integer id, #RequestBody Bmwsales v) throws JsonProcessingException{
ObjectMapper objMapper=new ObjectMapper();
JsonPatchBuilder jsonPatchBuilder=Json.createPatchBuilder();
JsonPatch jsonPatch=jsonPatchBuilder.replace("/templocation",v.getTemplocation()).replace("/rfidtag", v.getRfidtag()).build();
Bmwsales vehicle=bmwService.getVin(id).orElseThrow(ResourceNotFoundException::new);
BmwsalesUpdate veh=oMapper.asInput(vehicle);
BmwsalesUpdate h=patchHelp.patch(jsonPatch, veh, BmwsalesUpdate.class);
oMapper.INSTANCE.update(vehicle, h);
return new ResponseEntity<>(h, HttpStatus.OK);
I would like to know how to fix this....
error:
xhr.js:178 PATCH http://...../bmwsales/updateWeb/69406/ 422
Looks like this is happening....there currently is no value in templocation and I'm trying to update it....how do I change my object mapper to accept that the current value can be null? I'm thinking this is the problem...maybe...
javax.json.JsonException: '{"id":69406,"rfidtag":"E200420C71A06015010B6362"}' contains no value for name 'templocation'
OK I think I fixed this....I changed my Jackson Config to comment out the setdefaultpropertyinclusion to only include non null. Now that is ok. But now instead of updating the 2 fields that I have indicated the json patch is trying to update the whole object. How do I restrict this?
Maybe you can set the consumes attribute inside the #PatchMapping with the value APPLICATION_JSON_VALUE in the class MediaType and the value field with the URL string, also check your Bmwsales object and see if all the fields matches with your request.
Hope this helps.

(JAVA, Elasticsearch) How can I get fields from SearchResponse?

I just wonder how I get fields from SearchResponse which is result of my query.
Below is my query:
{"size":99,"timeout":"10s","query":{"bool":{"filter":[{"bool":{"must":[{"range":{"LOG_GEN_TIME":{"from":"2018-11-01 12:00:01+09:00","to":"2018-11-01 23:59:59+09:00","include_lower":true,"include_upper":true,"boost":1.0}}},{"wrapper":{"query":"eyAiYm9vbCIgOiB7ICJtdXN0IiA6IFsgeyAidGVybSIgOiB7ICJBU1NFVF9JUCIgOiAiMTAuMTExLjI1Mi4xNiIgfSB9LCB7ICJ0ZXJtIiA6IHsgIkFDVElPTl9UWVBFX0NEIiA6ICIyIiB9IH0sIHsgInRlcm0iIDogeyAiRFNUX1BPUlQiIDogIjgwIiB9IH0gXSB9IH0="}}],"adjust_pure_negative":true,"boost":1.0}}],"adjust_pure_negative":true,"boost":1.0}},"_source":{"includes":["LOG_GEN_TIME","LOG_NO","ASSET_NO"],"excludes":[]},"sort":[{"LOG_GEN_TIME":{"order":"desc"}},{"LOG_NO":{"order":"desc"}}]}
and when I query this, like below:
SearchResponse searchResponse = request.get();
I got right result:
{
"took":1071,
"timed_out":false,
"_shards":{
"total":14,
"successful":14,
"skipped":0,
"failed":0
},
"_clusters":{
"total":0,
"successful":0,
"skipped":0
},
"hits":{
"total":2,
"max_score":null,
"hits":[
{
"_index":"log_20181101",
"_type":"SEC",
"_id":"1197132746951492963",
"_score":null,
"_source":{
"ASSET_NO":1,
"LOG_NO":1197132746951492963,
"LOG_GEN_TIME":"2018-11-01 09:46:28+09:00"
},
"sort":[
1541033188000,
1197132746951492963
]
},
{
"_index":"log_20181101",
"_type":"SEC",
"_id":"1197132746951492963",
"_score":null,
"_source":{
"ASSET_NO":2,
"LOG_NO":1197337264704454700,
"LOG_GEN_TIME":"2018-11-01 23:00:06+09:00"
},
"sort":[
1541080806000,
1197337264704454700
]
}
]
}
}
To use this result, I need to map this by field and value.
I think there's a way to map the field and value to the 'fields' parameter so that we could use it nicely, but I cannot find.
I hope I can use the result like this way:
SearchHit hit = ...
Map<String, SearchHitField> fields = hit.getFields();
String logNo = fields.get("LOG_NO").value();
And It seems like this is the common way to use..
Or am I misunderstanding something? Tell me other way if there's better way, please.
Any comment would be appreciated. Thanks.
I'm not clear what client you are using to query elastic. If you are using elasticsearch high level rest client then you can loop through hits and to get source you can use hit.getSourceAsMap() to get the key value of fields.
For your comment:
Firstly create a POJO class which corresponds to _source (i.e. index properties; the way data is store in elastic)
The use hit.getSourceAsString() to get _source in json format.
Use jackson ObjectMapper to map json to your pojo
Assuming you created a POJO class AssetLog
SearchHit[] searchHits = searchResponse.getHits().getHits();
for (SearchHit searchHit : searchHits) {
String hitJson = searchHit.getSourceAsString();
ObjectMapper objectMapper = new ObjectMapper();
AssetLog source = objectMapper.readValue(hitJson, AssetLog.class);
//Store source to map/array
}
Hope this helps.

How to return JsonArray in a Java Response object

I'm trying to implement java based web-service server which returns to Json and java script based web-service client. Here is my java part :
#Path("/myapp")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class MyRecommender {
#POST
#Path("/recs")
public Response getRecommendations() {
//Assume recommendation List contains some Recommendation objects
//I removed it for simplicity.
List<Recommendation> recommendation;
JsonArrayBuilder builder = Json.createArrayBuilder();
for (Recommendation rec : recommendations) {
builder.add(Json.createObjectBuilder().add("firstPersonName", "\"" + rec.getFirstPerson().getName() + "\"")
.add("firsPersonURL", "\"" + rec.getFirstPerson().getURL() + "\"")
.add("secondPersonName", "\"" + rec.getSecondPerson().getName() + "\"")
.add("secondPersonURL", "\"" + rec.getSecondPerson().getURL() + "\"")
.add("score", "\"" + rec.getSimilarity() + "\""));
}
JsonArray jsonData = builder.build();
return Response.ok(jsonData, MediaType.APPLICATION_JSON).header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "POST").allow("OPTIONS").build();
}
}
}
Now, when I call this function from my js client, I got :
POST http://localhost:8080/myapp/recs 500 (Request failed.)
But when I replace the for loop and return with the following code snipped I got response in my js correctly.
Changed part :
// remove for loop and change jsonData type.
String jsonData = "{\"name\":\"John\"}";
return Response.ok(jsonData, MediaType.APPLICATION_JSON).header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "POST").allow("OPTIONS").build();
So, I wonder what might be problem ? Since its my first time with web-services, I have some difficulty to debug my code.
EDIT
By the way, I get also another error when I try to first version of getRecommendations() functions(with loop)
XMLHttpRequest cannot load http://localhost:8080/myapp/recs.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:3000' is therefore not allowed access.
The response had HTTP status code 500.
But as I said, When I remove the loop and put second code snipped into getRecommendations() functions, both of the errors are gone and I get the response in my website.
EDIT2
When I changed the loop and return statement of getRecommendations() function with the below I again get the same errors
JsonObject value = Json.createObjectBuilder()
.add("name", "John").build();
return Response.ok(value, MediaType.APPLICATION_JSON).header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "POST").allow("OPTIONS").build();
EDIT 3
As far as I understood, createObjectBuilder().build() or JsonArrayBuilder.build() return an JSON object and below of this build statement in my getRecommendations() function is not even run. So, I think my problem how could I give Access-Control-Allow-Origin permission to this object?
If it is ok to use a third Party Library, using Google's Gson could be an efficient solution in this case. When converting Models to JSON it is quite handy.
What you can do is something like this.
ArrayList<Recommendation> recommendationList = new ArrayList<Recommendation>();
Gson gson = new Gson();
String jsonData = gson.toJson(reccomendationList);
To use it as a dependency in your POM file you could do this.
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.3.1</version>
<scope>compile</scope>
</dependency>
You should try simplest approach:
return Response.ok().entity(recommendation).header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "POST").allow("OPTIONS").build();

how to manipulate HTTP json response data in Java

HttpGet getRequest=new HttpGet("/rest/auth/1/session/");
getRequest.setHeaders(headers);
httpResponse = httpclient.execute(target,getRequest);
entity = httpResponse.getEntity();
System.out.println(EntityUtils.toString(entity));
Output as follows in json format
----------------------------------------
{"session":{"name":"JSESSIONID","value":"5F736EF0A08ACFD7020E482B89910589"},"loginInfo":{"loginCount":50,"previousLoginTime":"2014-11-29T14:54:10.424+0530"}}
----------------------------------------
What I want to know is how to you can manipulate this data using Java without writing it to a file?
I want to print name, value in my code
Jackson library is preferred but any would do.
thanks in advance
You may use this JSON library to parse your json string into JSONObject and read value from that object as show below :
JSONObject json = new JSONObject(EntityUtils.toString(entity));
JSONObject sessionObj = json.getJSONObject("session");
System.out.println(sessionObj.getString("name"));
You need to read upto that object from where you want to read value. Here you want the value of name parameter which is inside that session object, so you first get the value of session as JSONObject using getJSONObject(KeyString) and read name value from that object using function getString(KeyString) as show above.
May this will help you.
Here's two ways to do it without a library.
NEW (better) Answer:
findInLine might work even better. (scannerName.findInLine(pattern);)
Maybe something like:
s.findInLine("{"session":{"name":"(\\w+)","value":"(\\w+)"},"loginInfo":{"loginCount":(\\d+),"previousLoginTime":"(\\w+)"}}");
w matches word characters (letters, digits, and underscore), d matches digits, and the + makes it match more than once (so it doesnt stop after just one character).
Read about patterns here https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html
OLD Answer:
I'm pretty sure you could use a scanner with a custom delimiter here.
Scanner s = new Scanner(input).useDelimiter("\"");
Should return something like:
{
session
:{
name
:
JSESSIONID
,
value
:
5F736EF0A08ACFD7020E482B89910589
And so on. Then just sort through that list/use a smarter delimiter/remove the unnecessary bits.
Getting rid of every other item is a pretty decent start.
https://docs.oracle.com/javase/7/docs/api/java/util/Scanner.html has info on this.
I higly recomend http-request built on apache http api.
private static final HttpRequest<Map<String, Map<String, String>>> HTTP_REQUEST = HttpRequestBuilder.createGet(yourUri, new TypeReference<Map<String, Map<String, String>>>{})
.addDefaultHeaders(headers)
.build();
public void send(){
ResponseHandler<Map<String, Map<String, String>>> responseHandler = HTTP_REQUEST.execute();
Map<String, Map<String, String>> data = responseHandler.get();
}
If you want use jackson you can:
entity = httpResponse.getEntity();
ObjectMapper mapper = new ObjectMapper();
Map<String, Map<String, String>> data = mapper.readValue(entity.getContent(), new TypeReference<Map<String, Map<String, String>>>{});

Categories