Invalid Expression exception for JsonPath in RestAssured - rest-assured

We are using restassured for API automation in our project . I have sample response for which I tested JsonPath expression on https://www.jsonquerytool.com/. My Json expression is - $[*]['tags'][?(#.id==0)].
I am getting proper output when I tried expression on JsonQuerytool. But when I try same in below code , I get invalid expression message -
JsonPath jsonPathEvaluator = response.jsonPath();
ArrayList result = jsonPathEvaluator.get("$[*]['tags'][?(#.id==0)]");
Above code throws exception .
Can anyone tell me how can I programmatically query the response using JsonPathEvaluator ?
P.S - Response not pasted as it was very huge.

Since your issue is not in any particular query (expression does not matter) I'm answering using example from the link you provided.
RestAssured uses some special JsonPath library (which uses its own syntax in some cases) called GPath. So if you have json like this:
{
"key": "value",
"array": [
{
"key": 1
},
{
"key": 2,
"dictionary": {
"a": "Apple",
"b": "Butterfly",
"c": "Cat",
"d": "Dog"
}
},
{
"key": 3
}
]
}
And expect you would use query like this: $.array[?(#.key==2)].dictionary.a
Then for RestAssured case your query would be like this: array.findAll{i -> i.key == 2}.dictionary.a
So the complete code example would be:
public static void main(String[] args) {
JsonPath jsonPath = RestAssured
.get("http://demo1954881.mockable.io/gath")
.jsonPath();
List<String> resp = jsonPath.get("array.findAll{i -> i.key == 2}.dictionary.a");
System.out.println(resp);
}

Related

ktor dynamic keys serialization and json preprosesing

Is there anyway in Ktor to alter a json before deserialization process? In the example below there is a json with dynamic keys in which i need to remove the "token" key as it is of a string type instead of an object type as the other keys.
When i urn the code i get the following error:
Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Expected class kotlinx.serialization.json.JsonObject as the serialized body of shell.remoting.Market, but had class kotlinx.serialization.json.JsonLiteral
I'm not sure if there is another better way to do it. Any suggestion will be appreciated, thanks
object MarketMapSerializer :
JsonTransformingSerializer<Map<String, Market>>(MapSerializer(String.serializer(), Market.serializer())) {
override fun transformSerialize(element: JsonElement): JsonElement =
JsonObject(element.jsonObject.filterNot { (k, _) ->
k == "token"
})
}
#Serializable
data class Market(
val stations: List<Station>,
)
#Serializable
data class Station(
#JsonNames("iata_code")
val iataCode: String,
val destinations: List<String>,
)
fun main() {
val jsonString = """
{
"Republica Dominicana": {
"stations": [
{
"iata_code": "PUJ",
"destinations": [
"ADZ",
"BAQ",
"VVC"
]
}
]
},
"Brasil": {
"stations": [
{
"iata_code": "AJO",
"destinations": [
"ADZ",
"BAQ",
"VVC"
]
}
]
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkb3RSRVogQVBJIiwianRpIjoiYTVhZDM4NmYtOGViNy0yOWQ5LWZkMGYtM2Q3MzQwZmRhOGI2IiwiaXNzIjoiZG90UkVaIEFQSSJ9.V2YfXCt9r7Tzae9SYSoj-qIyxjRc9YoE2XPoIQQNI9U"
}
""".trimIndent()
println(
Json.decodeFromString(
MarketMapSerializer,
jsonString
)
)
}
Just replace transformSerialize with the transformDeserialize since you're doing deserialization.

Zapier - add data to JSON response (App development)

We are creating a Zapier app to expose our APIs to the public, so anyone can use it. The main endpoint that people are using returns a very large and complex JSON object. Zapier, it looks like, has a really difficult time parsing nested complex JSON. But it does wonderful with a very simple response object such as
{ "field": "value" }
Our data that is being returned has this structure and we want to move some of the fields to the root of the response so it's easily parsed by Zapier.
"networkSections": [
{
"identifier": "Deductible",
"label": "Deductible",
"inNetworkParameters": [
{
"key": "Annual",
"value": " 600.00",
"message": null,
"otherInfo": null
},
{
"key": "Remaining",
"value": " 600.00",
"message": null,
"otherInfo": null
}
],
"outNetworkParameters": null
},
So, can we do something to return for example the remaining deductible?
I got this far (adding outputFields) but this returns an array of values. I'm not sure how to parse through this array either in the Zap or in the App.
{key: 'networkSections[]inNetworkParameters[]key', label: 'xNetworkSectionsKey',type: 'string'},
ie this returns an array of "Annual", "Remaining", etc
Great question. In this case, there's a lot going on, and outputFields can't quite handle it all. :(
In your example, inNetworkParameters contains an array of objects. Throughout our documentation, we refer to these as line items. These lines items can be passed to other actions, but the different expected structures presents a bit of a problem. The way we've handled this is by letting users map line-items from one step's output to another step's input per field. So if step 1 returns
{
"some_array": [
{
"some_key": "some_value"
}
]
}
and the next step needs to send
{
"data": [
{
"some_other_key": "some_value"
}
]
}
users can accomplish that by mapping some_array.some_key to data.some_other_key.
All of that being said, if you want to always return a Remaining Deductible object, you'll have to do it by modifying the result object itself. As long as this data is always in that same order, you can do something akin to
var data = z.JSON.parse(bundle.response.content);
data["Remaining Deductible"] = data.networkSections[0].inNetworkParameters[1].value;
return data;
If the order differs, you'll have to implement some sort of search to find the objects you'd like to return.
I hope that all helps!
Caleb got me where I wanted to go. For completeness this is the solution.
In the creates directory I have a js file for the actual call. The perform part is below.
perform: (z, bundle) => {
const promise = z.request({
url: 'https://api.example.com/API/Example/' + bundle.inputData.elgRequestID,
method: 'GET',
headers: {
'content-type': 'application/json',
}
});
return promise.then(function(result) {
var data = JSON.parse(result.content);
for (var i=0; i<data.networkSections.length; i++) {
for (var j=0; j<data.networkSections[i].inNetworkParameters.length; j++) {
// DEDUCT
if (data.networkSections[i].identifier == "Deductible" &&
data.networkSections[i].inNetworkParameters[j].key == "Annual")
data["zAnnual Deductible"] = data.networkSections[i].inNetworkParameters[j].value;
} // inner for
} // outer for
return data;
});

How to get a sub-field of a struct type map, in the search response of YQL query in Vespa?

Sample Data:
"fields": {
"key1":0,
"key2":"no",
"Lang": {
"en": {
"firstName": "Vikrant",
"lastName":"Thakur"
},
"ch": {
"firstName": "维克兰特",
"lastName":"塔库尔"
}
}
}
Expected Response:
"fields": {
"Lang": {
"en": {
"firstName": "Vikrant",
"lastName":"Thakur"
}
}
}
I have added the following in my search-definition demo.sd:
struct lang {
field firstName type string {}
field lastName type string {}
}
field Lang type map <string, lang> {
indexing: summary
struct-field key {
indexing: summary | index | attribute
}
}
I want to write a yql query something like this (This doesn't work):
http://localhost:8080/search/?yql=select Lang.en from sources demo where key2 contains 'no';
My temporary workaround approach
I have implemented a custom searcher in MySearcher.java, through which I am able to extract the required sub-field and set a new field 'defaultLang', and remove the 'Lang' field. The response generated by the searcher:
"fields": {
"defaultLang": {
"firstName": "Vikrant",
"lastName":"Thakur"
}
}
I have written the following in MySearcher.java:
for (Hit hit: result.hits()) {
String language = "en"; //temporarily hard-coded
StructuredData Lang = (StructuredData) hit.getField("Lang");
Inspector o = Lang.inspect();
for (int j=0;j<o.entryCount();j++){
if (o.entry(j).field("key").asString("").equals(language)){
SlimeAdapter value = (SlimeAdapter) o.entry(j).field("value");
hit.setField("defaultLang",value);
break;
}
}
hit.removeField("Lang");
}
Edit-1: A more efficient way instead is to make use of the Inspectable interface and Inspector, like above (Thanks to #Jo Kristian Bergum)
But, in the above code, I am having to loop through all the languages to filter out the required one. I want to avoid this O(n) time-complexity and make use of the map structure to access it in O(1). (Because the languages may increase to 1000, and this would be done for each hit.)
All this is due to the StructuredData data type I am getting in the results. StructureData doesn't keep the Map Structure and rather gives an array of JSON like:
[{
"key": "en",
"value": {
"firstName": "Vikrant",
"lastName": "Thakur"
}
}, {
"key": "ch",
"value": {
"firstName": "维克兰特",
"lastName": "塔库尔"
}
}]
Please, suggest a better approach altogether, or any help with my current one. Both are appreciated.
The YQL sample query I guess is to illustrate what you want as that syntax is not valid. Picking a given key from the field Lang of type map can be done as you do in your searcher but deserializing into JSON and parsing the JSON is probably inefficient as StructuredData implements the Inspectable interface and you can inspect it directly without the need to go through JSON format. See https://docs.vespa.ai/documentation/reference/inspecting-structured-data.html

Extracting value of a node in Java using contains with JsonPath in RestAssured

I have to extract value of book title using JsonPath in RestAssured in Java from following json response
{
"spec": {
"groups": [
{
"name": "book",
"title": "classic-books:1.0.2"
},.......
]
}
}
I am looking to use contains to get the book with a specific title.Please help.
Assume you have response with JSON in it:
response.body().jsonPath().get("spec.groups[i].title");

What am I doing wrong in this QBO v3 Reports API query?

When I use the following query, I get a good response (with only the first 5 days of May, so apparently the default is not 'This Fiscal Year-to-date' as the documentation suggests, but I digress):
https://quickbooks.api.intuit.com/v3/company/0123456789/reports/CustomerSales
When I add parameters, I get an oauth exception. For example:
https://quickbooks.api.intuit.com/v3/company/0123456789/reports/CustomerSales?start_date='2013-01-01'&end_date='2014-05-06'
Gives me this:
{
"Fault": {
"type": "AUTHENTICATION",
"Error": [
{
"Message": "message=Exception authenticating OAuth; errorCode=003200; statusCode=401",
"code": "3200"
}
]
},
"requestId": "[redacted]",
"time": "[redacted]"
}
This gives me the same result:
https://quickbooks.api.intuit.com/v3/company/0123456789/reports/CustomerSales?date_macro='This Fiscal Year'
So does this:
https://quickbooks.api.intuit.com/v3/company/148305798/reports/CustomerSales?accounting_method='Accrual'
I figure I'm missing something small. I'm not changing any of the headers or any of the other request details...just the url.
I tried without the single quotes around the dates and other params too.
What am I breaking?
Are you including the data to the right of the ? in the URL in the "base" string and are you sorting it with the other parameters?
I've tried this report using java devkit.
It worked fine for me. PFB details.
Request URI - https://quickbooks.api.intuit.com/v3/company/1092175540/reports/CustomerSales?accounting_method=Accrual&start_date=2014-01-01&requestid=61234ddb7e14ce2a5fe4e2f0318b31c&minorversion=1&
My test company file is empty.. That's why got the following JSON response.
{
"Header":{
"Time":"2014-05-06T20:42:08.783-07:00",
"ReportName":"CustomerSales",
"ReportBasis":"Accrual",
"StartPeriod":"2014-05-01",
"EndPeriod":"2014-05-06",
"SummarizeColumnsBy":"Total",
"Currency":"USD"
},
"Columns":{
"Column":[
{
"ColTitle":"",
"ColType":"Customer"
}
]
},
"Rows":{
"Row":[
{
"ColData":[
{
"value":"TOTAL"
}
],
"group":"GrandTotal"
}
]
}
}
JAVA code
void testCustomerSalesReport(Context context) {
Config.setProperty(Config.SERIALIZATION_RESPONSE_FORMAT, "json");
ReportService service = new ReportService(context);
service.setStart_date("2014-01-01");
service.setAccounting_method("Accrual");
Report report = null;
try {
report = service.executeReport(ReportName.CUSTOMERSALES.toString());
} catch (FMSException e) {
e.printStackTrace();
}
}
API Doc Ref - https://developer.intuit.com/docs/0025_quickbooksapi/0050_data_services/reports/customersales
Hope it will be useful.
Thanks

Resources