Forum Discussion

richie's avatar
richie
Community Hero
8 months ago

GroovyScript - Extract specific number of attribute values from variable content response

Hey!

I've checked my groovy script notes which contains essentially all the groovy script snippets people have ever helped me with over the years - mostly from nmrao if I'm being honest - but I haven't got an example of what I need - sometimes I can expand the examples I already have to do what I need, but not this time.

I have a GET response JSON payload that can have 1 or many records in the response. 

Each record in the response will have an 'id' attribute value (JSONPATH --> x.data[0].id)

The 'data' array can have 1 or many records, so JSONPATH for the id attribute value for first record this would be x.data[0].id, but there could be 50 records or more (50 records, the JSONPATH of the id associated with the 50th record would be x.data[49].id)

I won't need to save all 50, or 100, or however many records there will be.  

I'd like to be able to specify the number of id values I save for later.

I want to save these 'id' values at TestSuite level for later. 

I'm not sure how many 'id' values I'll need - maybe only 5 or maybe 10 - I don't know yet, so I'd like to be able to specify how many 'id' values I extract and save on the TestSuite.


This is the groovy script so far - as I say above - it's all courtesy of nmrao

//Script Assertion
def json = new groovy.json.JsonSlurper().parseText(context.response)
//find all 'id' attribute values
def id_list = json.data.'id'.findAll() 
//List the 'id' attribute item count
log.info id_list.size
//Iterate thru each id, then stick them in a map, then once they're in a map, grab X instances of the 'id' values and split the map out and save them at TestSuite level
id_list.each { id ->

And that's as far as I've got.

I'm guessing I need to count the number of records so I don't try and grab more 'id' attribute values than actually exist causing the script to fail. I think I need a map and I've been googling - but it's beyond me - all the groovy script with maps I've got examples of,  mix multiple methods together and I've never been able to read exactly what's occurring in those lines of the groovy.  I know the groovy to save off a single property to TestSuite level for later use - but I don't know how to save off a variable sized number of attributes into separate properties for later use - especially as I'd like to grab a certain number of 'id' values from the total available - maybe from the first 10 records out of X returned.

I've attached the response payload that includes just a single record

Can anyone give me a steer please?

As always - I genuinely appreciate all/any help anyone can give me!

Cheers,

Rich

 

  • richie 

    You can quickly try this groovy to understand how this can be achieved in general.

    Follow inline comments:

    def range = 1..10
    
    println "Items in the range: $range"
    
    
    def startingIndex=0
    
    //Set this value, how many values to be stored 
    def endIndex=4
    
    //This is to ensure not to grab more than present in the list
    assert endIndex <= range.size(), 'Choose the endIndex less than list size'
    
    //Now get the sub list from original list
    def mySubList = range.subList(startingIndex,endIndex)
    println mySubList
    
    //In order to save data as custom property, coierce it to String
    def mySubListToString = mySubList.join(',')
    println mySubListToString //Save this at suite level
    
    //Convert string back to list when needed later
    def stringToList = mySubListToString.split()
    println stringToList

    If running the above from the ReadyAPI / SoapUI, use log.info instead of println

    Hope you knew how to store mySubListToString into Test Suite custom property.

    Note that all the properties in ReadyAPI are stored as string.

  • richie 

    All it needs here is to replace

    def range = 1..10

    with

    def json = new groovy.json.JsonSlurper().parseText(context.response)

    def range = json.data.id.findAll()

    Change endIndex value depending on how many values that you need. 

    In order to store values at test suite level, add below statement at line number 20.

    context.testCase.testSuite.setPropertyValue('MY_SUBLIST', mySubListToString)

     

    And lines #22, 23, 24 are not needed here i.e., script assertion, as those to fetch and convert string to list. Instead use the when you need those values later.

    By the way, first it requires to fetch the MY_SUBLIST value stored at test suite level, so add below statement before using those lines.

    def mySubListToString = context.testCase.testSuite.getPropertyValue('MY_SUBLIST)

    Now it has all code it required with your data to achieve what is mentioned in the original question.

    NOTE: I am referring the line numbers here in this response from my original response's code.

  • nmrao's avatar
    nmrao
    Champion Level 3

    richie 

    You can quickly try this groovy to understand how this can be achieved in general.

    Follow inline comments:

    def range = 1..10
    
    println "Items in the range: $range"
    
    
    def startingIndex=0
    
    //Set this value, how many values to be stored 
    def endIndex=4
    
    //This is to ensure not to grab more than present in the list
    assert endIndex <= range.size(), 'Choose the endIndex less than list size'
    
    //Now get the sub list from original list
    def mySubList = range.subList(startingIndex,endIndex)
    println mySubList
    
    //In order to save data as custom property, coierce it to String
    def mySubListToString = mySubList.join(',')
    println mySubListToString //Save this at suite level
    
    //Convert string back to list when needed later
    def stringToList = mySubListToString.split()
    println stringToList

    If running the above from the ReadyAPI / SoapUI, use log.info instead of println

    Hope you knew how to store mySubListToString into Test Suite custom property.

    Note that all the properties in ReadyAPI are stored as string.

  • nmrao's avatar
    nmrao
    Champion Level 3

    richie 

    In the original post, it is mentioned 

    //find all 'id' attribute values
    def id_list = json.data.'id'.findAll() 

    id_list, this is nothing but range in the example where there are list of ids.

    However, in the latest post, route_code is being used which is not available in the attached .txt file (original post)

    You just get all the id/code what ever applicable and assign them to range variable. Otherwise, please show relevant data.

  • nmrao's avatar
    nmrao
    Champion Level 3

    FYI

    If someone is  interested to learn groovy, with the help of below site one can learn gradually and quickly

    https://learnxinyminutes.com/docs/groovy/

    I've been googling to try and understand what this '{ prop ->' represents in groovy

    The exact answer to the above question is there under the Collections and maps sections.

    Just try the code  in the below site  quickly here https://onecompiler.com/groovy

    Suggesting the site to try because, don't want your readyapi project fill with some random examples which don't belong there.

     

    Yet to go into the details of your response.

  • nmrao's avatar
    nmrao
    Champion Level 3

    richie 

    All it needs here is to replace

    def range = 1..10

    with

    def json = new groovy.json.JsonSlurper().parseText(context.response)

    def range = json.data.id.findAll()

    Change endIndex value depending on how many values that you need. 

    In order to store values at test suite level, add below statement at line number 20.

    context.testCase.testSuite.setPropertyValue('MY_SUBLIST', mySubListToString)

     

    And lines #22, 23, 24 are not needed here i.e., script assertion, as those to fetch and convert string to list. Instead use the when you need those values later.

    By the way, first it requires to fetch the MY_SUBLIST value stored at test suite level, so add below statement before using those lines.

    def mySubListToString = context.testCase.testSuite.getPropertyValue('MY_SUBLIST)

    Now it has all code it required with your data to achieve what is mentioned in the original question.

    NOTE: I am referring the line numbers here in this response from my original response's code.

    • richie's avatar
      richie
      Community Hero

      nmrao saves the day (despite the fact you don't even have ReadyAPI to debug your code)

      //Script Assertion - all code below courtesy of Rao
      
      //extract the request's response and pass it to the variable entitled json
      def json = new groovy.json.JsonSlurper().parseText(context.response)
      
      //find all 'id' attribute values
      def id_list = json.data.'id'.findAll() 
      
      //List the 'id' attribute item count
      log.info id_list.size
      
      //define starting index value
      def startingIndex=0
      
      //Set this value, how many values to be stored 
      def endIndex=4
      
      //This is to ensure not to grab more than present in the list
      assert endIndex <= id_list.size(), 'Choose the endIndex less than list size'
      
      //Now get the sub list from original list
      def mySubList = id_list.subList(startingIndex,endIndex)
      log.info mySubList
      
      //In order to save data as custom property, coerce it to String
      def mySubListToString = mySubList.join(',')
      log.info mySubListToString
      
      //save off the comma separated string values as a custom testSuite Property entitled MY_SUBLIST
      context.testCase.testSuite.setPropertyValue('MY_SUBLIST', mySubListToString)

      This is brilliant - exactly what I need.

      I'm gonna need to extract the values for later use - I might have a question or two on that - just tried a quick go using split() and it didn't work like I'd hoped it would, but lemme give it a good go first and I might be getting back to you.

      Marking this as fixed and your solution as The Solution.

      Again - thanks so much,

      rich

      • nmrao's avatar
        nmrao
        Champion Level 3

        richie 

        Not sure what it should be called by repeating my script in your response and marking it as solution .

         I must express that this is entirely inappropriate and completely unacceptable.

        Thank you for your understanding.

  • richie's avatar
    richie
    Community Hero

    Ah nmrao  - that is brilliant man - really - I can use this and extend it for loads of stuff I'm working on - and yeah - I remember ReadyAPI groovy log.info rather than other groovy/java println and I'm good extracting to testsuite property and I've been casting between numerics and strings using .toInteger() / .toString() methods on a number of scripts I've been working on in the last week  - it was EVERYTHING else up to that point I was stuffed on. ;) 

    again - you are a Life Saver!

    cheers,

    rich

    • richie's avatar
      richie
      Community Hero

      Hey nmrao 

      Ok - I'm still struggling - if I'm understanding the code correctly - your code starts off as follows via a Script Assertion:

      def json = new groovy.json.JsonSlurper().parseText(context.response)
      log.info json
      
      def startingIndex=0
      
      //Set this value, how many values to be stored 
      def endIndex=4 
      
      assert endIndex <= range.size(), 'Choose the endIndex less than list size' 
      
      //Now get the sub list from original list 
      def mySubList = range.subList(startingIndex,endIndex) 
      log.info mySubList
      
      //In order to save data as custom property, coierce it to String
      def mySubListToString = mySubList.join(',')
      log.info mySubListToString //Save this at suite level

      so the JSONPATH of the first route_code out of the 4 I care about is  x.json.data[0].route_code

      HOW do I match the startingIndex variable (which you've got declared above) to json.data[0].route_code) so that the other 3 route_code values (with values json.data[1].route_code, json.data[2].route_code, json.data[3].route_code) are extracted also?

      Your comment on the line declaring the mySubListToString variable is the bit where I save my data as a custom testsuite property - but at this point I've got no data - I haven't yet extracted the x number of route_codes (4 in the case of the above script defined by the endIndex variable) - this is the bit I'm struggling with.

       

      Just so you know I'm not just letting you do all the heavy lifting without trying to solve myself - I did try playing around with the following to see if I could get a little further along (or at least increase my understanding) - cos the code below is based on what you've helped me out with before - I was hoping I could edit it to do what I need, but it only logs the first instance of the route_code value - I thought lines 5 & 6 might extract all the route_codes - but I realised I'm not using the it() method - I did try adding .it() in various places but I couldn't get it to work - I really need to buy a groovy for dummies book!

      // Log the first route_code
      log.info('First (index zero) route_code = ' + json.data[0].route_code)
      
      // Log all route_codes
      json.data.every{ prop ->
      	log.info('route_code = ' + prop.route_code) 
      }
      
      // Log all route_code with index
      json.data.eachWithIndex(){ prop, idx ->
      	log.info('route_code ' + idx + ' = ' + prop.route_code)
      }
      

      Been years since I've used ReadyAPI! when you were helping me with my groovy before and it's really showing!

      As always - appreciate your help!

      Cheers,

      Rich

       

  • nmrao's avatar
    nmrao
    Champion Level 3

    Glad to know. Need anything? otherwise Closing this as resolved?

  • richie's avatar
    richie
    Community Hero

    Hey nmrao 

    My bad- I'm sorry for causing any confusion - I'm working with 60 different REST APIs which provide all the data for the 3 tiered web app I'm working on and all these APIs are starting to merge into one.

    I've been playing around with the Script Assertions on a different GET API - which is why I've caused confusion.

    Also - the question I'm asking is so transferrable when the number of records in your response payload changes - once I get this sorted I'm gonna be using this snippet of groovy for a lot of things.  

    So - to make things straightforward - lets keep going with the original API I mentioned before GET /v2/plans which returns a number of 'plans'.  (I've attached the same payload as the original one I attached in my first post).  Each plan has a unique identifier with JSONPATH--> x.data*.id (where the first record's id attribute has JSONPATH x.data[0].id)

    Spent the last 4 hours going through all my notes and googling and I managed to get a little further with the following code:

    //Script Assertion
    def json = new groovy.json.JsonSlurper().parseText(context.response) 
    
    // Log the first id 
    log.info('First (index zero) id = ' + json.data[0].id) 
    
    // Log all ids with index identifier (it's JSON so it starts at ZERO) 
    json.data.eachWithIndex(){ prop, idx -> 
    log.info('id ' + idx + ' = ' + prop.id) }

    So at this point, I have logged all the id values in the response along with it;s index identifier.

    I've been googling to try and understand what this '{ prop ->' represents in groovy  - I see it a lot but I've never really understood what it actually means, but I'm trying to understand so I can reuse it in the future but I didn't really understand.  It said that -> is used in a closure and this separates the "argument list from the body"

    Anyway - sorry going off at a tangent. - so I've now got a list of id's logged - but because of the user of this '{ prop ->' - I don't know how to convert the indexed id's (displayed in the logging) to a custom property. I've reminded myself of the getProperty() and setProperty() methods, but I'm struggling on this point, cos my list of indexed id's aren't actually assigned to a variable - so I'm a little lost there.

    Once I can do that - I was thinking I need to somehow assign the x.data[0].id attribute in my property list (once it's been extracted) to the startingIndex variable you have defined in the code you gave me (def startingIndex = 0) so then I can continue to use the rest of the code you gave me (see below)  - I think

    - I'm sure I'm missing several important issues like just cos I've a variable named as startingIndex with a numeric value - the groovy compiler isn't going to know that I want :

    x.data[0].id to be assigned startingIndex=0,

    x.data[1].id assigned startingIndex=1

    x.data[2].id assigned startingIndex=2

    x.data[3].id assigned startingIndex=3

    def range = 1..10
    
    log.info "Items in the range: $range"
    
    def startingIndex=0
    
    //Set this value, how many values to be stored 
    def endIndex=4
    
    //This is to ensure not to grab more than present in the list
    assert endIndex <= range.size(), 'Choose the endIndex less than list size'
    
    //Now get the sub list from original list
    def mySubList = range.subList(startingIndex,endIndex)
    log.info mySubList
    
    //In order to save data as custom property, coierce it to String
    def mySubListToString = mySubList.join(',')
    log.info mySubListToString //Save this at suite level
    
    //Convert string back to list when needed later
    def stringToList = mySubListToString.split()
    log.info stringToList

    So that's where I am right now.  

    I'm actually having a lot of fun - this is way more interesting than the rest of my job at the moment, so if you can give me a couple of pointers to steer me where I'm going wrong - I'd really appreciate it.

    thanks man - as always - can't thank you enough,

    Oh - I should also apologise - I've been kindof jumping all over the place trying to find a way through so I've trying various different approaches to do what I need - that probably isn't helping you understand what I'm talking about either!

    rich