SoapUI: Building JsonPath Expressions

The 5.2 release of SoapUI OS contains some significant changes.  Most obviously, there's been an overhaul of its basic look with new icons and some high-level actions (creating new test suites, importing suites, etc.) added to the main screen.  For the most part, these graphical changes are for ease-of-use and aesthetics; very little has really changed with basic workflows and 5.2 should be compatible with test suites created in pre-5.2 versions.  But in keeping with SoapUI's recent trend of improved support for non-SOAP services, several new JsonPath-based assertion types have also been added:

1) JsonPath Match - Verifies the value specified by a JsonPath expression matches some expected value

2) JsonPath Existence Match - Verifies the element specified by a JsonPath expression exists in the target JSON

3) JsonPath Count Match - Verifies the number of matching instances for a given JsonPath expression (if the expression evaluates to an array, the count is the length of the array) equals some expected value

4) JsonPath Regex Match - Similar to the basic JsonPath Match assertion, but the value specified by the JsonPath expression is matched to a regular expression

For those of you unfamiliar with it, JSON stands for JavaScript Object Notation.  Like XML, it provides a standardized way to represent complex data across different operating systems, architectures, etc.  Here's a typical JSON data block:
{"response":
   {
      "library":[
         {
            "title":"War and Peace",
            "author":"Tolstoy, Leo",
            "pages":1296,
            "tags":["literature","Russian","classic"]
         },
         {
            "title":"Harry Potter and the Chamber of Secrets",
            "author":"Rowling, J.K.",
            "pages":341,
            "tags":["fiction","fantasy"]
         },
         {
            "title":"In the Garden of Beasts",
            "author":"Larson, Erik",
            "pages":448,
            "tags":["non-fiction","history"]
         }
      ],
      "requestId":12345,
      "requestStatus":"OK"
   }      
}
This JSON defines an object (enclosed in braces in JSON) called response that consists of library, requestId, and requestStatus properties.  The library property is an array (enclosed in squared brackets) of objects representing books.  Each book object has a title, author, pages, and tags property; the tags property is itself another array of strings.

JsonPath is JSON's equivalent of XPath-- it provides a simple way to reference a particular value in JSON.  You can read a detailed description of JsonPath at http://goessner.net/articles/JsonPath, but I'll present some quick starting rules here with examples using the JSON data above.  JsonPath actually supports two different basic syntaxes, one using bracketed property names to navigate through successive levels of data (e.g., ['parent']['child']), and another using parent.child dot notation syntax similar to what's used in most programming languages.  For the sake of brevity, I'll focus on dot notation syntax in this post.

DescriptionExample ExpressionExample Result
To get the value of an object's property, separate the property name from the object name (or the portion of the JsonPath expression designating the object) with a dot-- again, if you're familiar with dot notation in programming, it's pretty much the same idea.response.requestStatusOK - The response object's requestStatus value
Zero-based indexing using brackets indicates a particular element in a JSON array.  Note the use of zero-based indexing (i.e., representing the first item with index 0, the second with index 1, etc.) is a key difference from XPath's one-based indexing.response.library[0].authorTolstoy, Leo - The author property of the first object in the library array
Using * instead of an index number with an array returns all elements in the array.response.library[*].title[War and Peace, Harry Potter and the Chamber of Secrets, In the Garden of Beasts] - A JSON array of every title in the library array
The JSON data root is designated by $, although this is optional in expressions that explicitly start with the first element of the target JSON data (the examples given above safely omit the $ since they start with the root response element).$.response.requestStatusOK - Equivalent to the first example; the requestStatus property of the response object
A pair of dots indicates a recursive search for the following property; i.e., the property can be found at any level beneath the property preceding the dots.  This is equivalent to double slashes in XPath.$..requestId12345 - The response object's requestId property (note we didn't have to explicitly designate the response object here)
You can access the length property of an array to determine the index of its last (or some position relative to the last) member.  In the example expression, @ represents the last evaluated element-- in this case, the library array-- in the bracketed expression.  Remember that because of zero-based indexing, the index of the last member of an array is one less than the array's length, so we subtract 1 from the length to find its index.  JsonPath is very particular about brackets and parentheses; the parentheses around the @.length - 1 expression may seem optional, but omitting them will result in an "invalid expression" error.$..library[(@.length - 1)].authorLarson, Erik - The author property of the last member in the library array.
You can also build basic predicate statements within squared brackets to filter on specific criteria.  The ? enclosed in brackets indicates a predicate, followed by a test expression in parentheses.  Within the predicate expression, the @ character acts as a placeholder for each array member being evaluated by the predicate.  So in this example expression we're starting with the result array, then evaluating each member's name property and returning those members where the title property matches 'War and Peace' (note the use of == instead of = to check for equality here).  Finally, for each array member that passed the predicate test, we get the value of its pages property.result.library[?(@.pages > 400)].title[War and Peace, In the Garden of Beasts] - The title property of every book object with a pages property greater than 400

If you'd like to play around with JSON expressions yourself, you can copy and paste the JSON data above into the JsonPath expression tester at http://jsonpath.curiousconcept.com.  One thing to keep in mind when working with the JsonPath expression tester: string values returned by JsonPath expressions in SoapUI are not enclosed in quotes, as they are with the JsonPath tester (the examples above show results as they appear in SoapUI).

7 comments:

  1. Great thanks, the info is helpful. Have one query regarding assertions. I am able to assert the response of my service through SOAP and I am using JsonPath Existence Match. I put a particular key that i need to validate in the expression box of the JsonPath Existence Match assertion and when I click on select from current button in the JsonPath Existence Match it by defaults takes value as false in the expected result. And the assertion works.
    But when I change the value of select from current as true then the assertion fails. Wanted to know what exactly does the 'Select from current' feature here means.

    ReplyDelete
    Replies
    1. Sorry for not replying sooner on this one. The 'Select from current' button is provided as a convenience when setting up assertions. It evaluates your assertion expression against the response and automatically populates the expected result value with the result of the evaluation. So in your case, whatever you're checking for does not exist or can't be found in the response using your JsonPath expression. When you click the button, the expected result is set to false, and the assertion passes because the expected result matches the actual result. When you change the expected result to true, though, the actual result (false) no longer matches the expected result, so the assertion fails.

      Delete
    2. How to proceed in this situation ?

      Delete
  2. {
    "deviceRegistrations": [],
    "availableDeviceDeRegistrations": 2,
    "availableDeviceRegistrations": 4
    }
    Hello All,

    This is my response how to assert this please anybody guide i have just started soapui

    ReplyDelete
    Replies
    1. you can use JsonSlurper

      get the required json node value and use if condition

      for Eg:

      import groovy.json.JsonSlurper

      def Response = context.expand('${#TestCase#Response}') // you will get response
      def Json1 = new JsonSluper().parseText(Response)

      def Value_availableDeviceDeRegistrations = Json1.availableDeviceDeRegistrations

      log.info availableDeviceDeRegistrations // you will get value 2

      if(Value_availableDeviceDeRegistrations == 2){
      log.info "Succeed"
      }
      else{
      log.info "fail"
      }

      Delete
  3. This is a wonderful blog Mike.
    How do I get the title of books with a tag "literature"

    I tried both of these but one worked :
    1) $.response.library[?(@.tags == "literature")].title
    2) $.response[*].library[?(@.tags.indexOf('literature')!=-1)].title

    Thank You
    Sam

    ReplyDelete

Please be respectful of others (myself included!) when posting comments. Unfortunately, I may not be able to address (or even read) all comments immediately, and I reserve the right to remove comments periodically to keep clutter to a minimum ("clean" posts that aren't disrespectful or off-topic should stay on the site for at least 30 days to give others a chance to read them). If you're looking for a solution to a particular issue, you're free to post your question here, but you may have better luck posting your question on the main forum belonging to your tool's home site (links to these are available on the navigation bar on the right).