WireMock is adding unexpected character when using CustomHelper - java

I am trying to use custom helpers to convert a list of Query Parameters form a request to a JSON response. The request is something like /scores?userIds=1&userIds=2 and my desired response is a list of JSON objects like the following:
[
{
"userId": 1,
"score": 80
},
{
"userId": 2,
"score": 200
}
]
To do so, I have implemented a helper called userScoreHelper:
Helper<List<String>> userScoreHelper = (context, options) -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
context.forEach(element ->
stringBuilder
.append("{\"score\": ")
.append(RandomGeneratorUtility.generateRandomNumber(0, 700) - 1)
.append(",\"userId\": ")
.append(element)
.append("},")
);
stringBuilder
.deleteCharAt(stringBuilder.lastIndexOf(","))
.append("]");
log.debug("Json response: {}", stringBuilder.toString());
return stringBuilder.toString();
};
And then I registered the helper in my WireMockServer configuration as follows:
#Bean(initMethod = "start", destroyMethod = "stop")
#Rule
public WireMockServer mockServer() {
return new WireMockServer(
options()
.port(9561)
.extensions(new ResponseTemplateTransformer(false,
"user-score-helper",
userScoreHelper))
);
}
The configuration of my stub looks like this:
public class ScoringCommunicatorMocks {
public static void setupMockScoringCommunicatorResponse(WireMockServer mockService) {
mockService.stubFor(
WireMock.get(WireMock.urlPathEqualTo("/scores"))
.willReturn(WireMock.aResponse()
//Using the registered handlebars helper here
.withBody("{{user-score-helper request.query.userIds}}")
.withHeader("Content-Type", "application/json")
.withStatus(200)
.withTransformers("response-template"))
);
}
}
And the output of the helper is a valid JSON according to my debugging logs, something like this:
[{"score": 304,"userId": 10},{"score": 28,"userId": 20}]
But when I try to run my unit tests I am getting an error message from JSON Decoder stating that my response contains an unexpected '&' character.
feign.codec.DecodeException: JSON conversion problem: Unexpected character ('&' (code 38)): was expecting double-quote to start field name; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unexpected character ('&' (code 38)): was expecting double-quote to start field name
at [Source: (PushbackInputStream); line: 1, column: 2] (through reference chain: java.util.ArrayList[0])
I suspect the WireMock is having problem with escaped " character because if I put the generated JSON object directly in the body like this .withBody("[{\"score\": 304,\"userId\": 10},{\"score\": 28,\"userId\": 20}]") it just works perfectly.
This is an awfully strange behavior and I have no clue how to get deeper at the root cause of this error (the rest of the stacktrace is irrelevant and adds nothing useful to what I have already stated here).
Any pointers or solutions are greatly appreciated.

I have actually managed to figure out what was wrong when I finished writing the question. It was indeed the the problem with escaping characters, all I needed to do was to use the "triple-mustaches" or "triple-stash" when I am calling the helper .withBody("{{{user-score-helper request.query.userIds}}}") instead of .withBody("{{user-score-helper request.query.userIds}}")

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!

Groovy DSL Spring cloud contract throwing java.lang.IllegalStateException for query parameter having Unicode Characters

I created a groovy DSL contract like below
import org.springframework.cloud.contract.spec.Contract
Contract.make {
final def NAME_REGEX = '[A-Za-z0-9\\u00C0-\\u00FF\'\\- ]{1,70}'
request {
method 'GET'
url('/api/getEmployess') {
queryParameters {
parameter 'name': $(c(regex(NAME_REGEX)), p('\u00CAdward J\u00F5hnson'))
}
}
headers {
contentType("application/json;charset=UTF-8")
}
}
response {
status OK()
body([
[
id : $(p(regex(nonBlank())), c('5a0eaf2012a9a12f1c98947a')),
name : fromRequest().query("name")
]
])
headers { contentType("application/json;charset=UTF-8") }
}
}
My service implementation returns 'name' and 'id' in response. In response, 'name' is Unicode value 'Êdward Jõhnson' which fails to match with the request parameter value.
I am getting below error -
Parsed JSON [[{"id":"5a0eaf2012a9a12f1c98947a","name":"Êdward Jõhnson"}]] doesn't match the JSON path [$[*][?(#.['name'] == 'Êdward Jõhnson')]]
java.lang.IllegalStateException: Parsed JSON [[{"id":"5a0eaf2012a9a12f1c98947a","name":"Êdward Jõhnson" }]] doesn't match the JSON path [$[*][?(#.['name'] == 'Êdward Jõhnson')]]
at com.toomuchcoding.jsonassert.JsonAsserter.check(JsonAsserter.java:228)
at com.toomuchcoding.jsonassert.JsonAsserter.checkBufferedJsonPathString(JsonAsserter.java:267)
I tried to pass the Unicode value in two ways in 'name' request query param -
Putting Unicode characters as Unicode numbers like in the above example -
parameter 'name': $(c(regex(NAME_REGEX)), p('\u00CAdward J\u00F5hnson'))
Putting Unicode characters as it is
parameter 'name': $(c(regex(NAME_REGEX)), p('Êdward Jõhnson'))
But for both cases, I am getting the same error. There looks some encoding issue because my value Êdward Jõhnson is changed to Êdward Jõhnson as mentioned in error.
Please help me to resolve this issue.
I found a workaround for this. In response 'name' field producer I put the same value which was in the request 'name' field producer. It was failing due to different encoding applied by groovy on unicode value. It's just a workaround to fix the problem, not a proper final solution.
import org.springframework.cloud.contract.spec.Contract
Contract.make {
final def NAME_REGEX = '^[A-Za-z0-9À-ÿ'\-\s]{1,70}$'
request {
method 'GET'
url('/api/getEmployess') {
queryParameters {
parameter 'name': $(c(regex(NAME_REGEX)), p('Êdward Jõhnson'))
}
}
headers {
contentType("application/json;charset=UTF-8")
}
}
response {
status OK()
body([
[
id : $(p(regex(nonBlank())), c('4b0eaf2012a9a12f1c98567c')),
name : $(p("Êdward Jõhnson"), c(fromRequest().body('$.name'))),
]
])
headers { contentType("application/json;charset=UTF-8") }
}
}

JSON contains unicode characters

My Java - Jersey framework REST API makes a call to another service which returns the following JSON response. I have logged the response from the child service in my logs, and I can see that the value of ErrorMessage contains a Unicode value like \u2019 instead of a single quote (').
{
"id": "SAMPLE",
"version": 1,
"status": {
"lastReceivedError": {
"ErrorDateTime": 1576588715,
"ErrorCode": "TEST3200",
"ErrorMessage": "We\u2019re sorry, the content is not available."
}
}
}
I have to map these values into my model and return as a JSON as well. I used GSON to convert the above JSON string into an object. And mapped the values from that object into my response object. My final outgoing JSON response is like below, wherein the single quote is appearing as question mark (?).
{
"MyResponse": {
"success": {
"lastReceivedError": {
"errorDateTime": "2019-12-17T13:18:35Z",
"errorCode": "TEST3200",
"errorMessage": "We?re sorry, the content is not available."
}
}
}
}
I believe there is something around encoding characters, but I am unable to fix the issue.
TL;DR
Seeing is not believing. It depends on the encoding in your environment.
Code snippet
Following code snippet shows to deserialize the JSON string (part of original response).
If the encoding of your environment is UTF-8, then Gson will convert it correctly without specifying encoding.
And if you already knew the original string was encoded with UTF-8, you will get different results if you view it with UTF-8 and ISO-8859-1.
String jsonStr = "{\"ErrorMessage\": \"We\u2019re sorry, the content is not available.\"}";
Gson gson = new Gson();
JsonObject data = gson.fromJson(jsonStr, JsonObject.class);
System.out.println(data.toString());
System.out.println(new String(jsonStr.getBytes("UTF-8"), "UTF-8"));
System.out.println(new String(jsonStr.getBytes("ISO-8859-1"), "UTF-8"));
Console output
{"ErrorMessage":"We’re sorry, the content is not available."}
{"ErrorMessage": "We’re sorry, the content is not available."}
{"ErrorMessage": "We?re sorry, the content is not available."}

How to use OpenFeign to get a pojo array?

I’m trying to use a OpenFeign client to hit an API, get some JSON, and convert it to a POJO array.
Previously I was simply getting a string of JSON and using Gson to convert it to the array like so
FeignInterface {
String get(Request req);
}
String json = feignClient.get(request);
POJO[] pojoArray = new Gson().fromJson(json, POJO[].class);
This was working. I would like to eliminate the extra step and have feign auto decode the JSON and return a POJO directly though, so I am trying this
FeignInterface {
POJO[] get(Request req);
}
POJO[] pojoArray = feignClient.getJsonPojo(request);`
I am running into this error
feign.codec.DecodeException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING at line 1 column 2 path $
Both methods used the same builder
feignClient = Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(FeignInterface.class, apiUrl);
Anyone have any ideas?
You have broken JSON payload. Before serialising you need to remove all unsupported characters. Feign allows this:
If you need to pre-process the response before give it to the Decoder,
you can use the mapAndDecode builder method. An example use case is
dealing with an API that only serves jsonp, you will maybe need to
unwrap the jsonp before send it to the Json decoder of your choice:
public class Example {
public static void main(String[] args) {
JsonpApi jsonpApi = Feign.builder()
.mapAndDecode((response, type) -> jsopUnwrap(response, type), new GsonDecoder())
.target(FeignInterface.class, apiUrl);
}
}
So, you need to do the same in your configuration and:
trim response and remove all whitespaces at the beginning and end of payload.
remove all new_line characters like: \r\n, \r, \n
Use online tool to be sure your JSON payload is valid and ready to be deserialised .

Java REST response value with missing spelling char

I have REST written in Java and the JSON response message is not valid.
I have messages defined in single file messages.properties. I expect that response should be something like that:
NOT_FOUND_PERSON = Person doesn't exist
However I got response with missing spelling:
['errorMsg': 'Person doesnt exist.']
Where is the problem? Cannot be due to wrong setup ResourceBundleMessageSource in config? I noticed there is missing UTF8 coding.
Is there problem with some hide escape function or whatever?
I found the solution. The problem was with quotation mark in messages.properties.
In default, spring uses message bundle to represent messages from properties file. Any occurences of quotation mark must be escaped by single quote otherwise it won't be displayed properly.
This
test.message2={0}'s message
must be replaced by this
test.message2={0}''s message
Resource:
https://www.mscharhag.com/java/resource-bundle-single-quote-escaping
I used double quotes in the JSON string.
{
"objectDetail": {
"id": "1",
"anotherId": "1",
"errorText": "This element doesn't exist."
}
}
And I tried with your data.
['errorMsg': 'Person doesn't exist.']
This is my example, it returns an object which is converted to JSON String.
#RestController
#RequestMapping(method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public class JsonRestController {
#RequestMapping("/api/{id}")
public ResponseEntity<RestObject> getModel(#PathVariable Long id) {
RestObject restObject = restService.getModel(id);
return new ResponseEntity<>(restObject, HttpStatus.OK);
}
}
It escapes double quotes.
{
"timestamp": "2018-01-23 13:37:53",
"title": "Rest object. This is the error does\"t and don't\" aaa.",
"fullText": "This is the full text. ID: 3",
"id": 3,
"value": 0.449947838273684
}
https://stackoverflow.com/a/15637481/4587961
https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf

Categories