Any clever ideas/tools for a situation where I'm testing a JSON-returning API, and I don't really care to check each and every field, but just want to check a subset of them? For example, if I decode the response into an array, it might be something like:

array(
  'foo' => 'bar',
  'something' => 'else',
  'data' => array(
    'key' => 'test',
    'value' => 1234
  )
)

If I then had an "expected results" array like so...

array(
  'foo' => 'bar'
  'data' => array(
    'value' => 1234
  )
)

...can you imagine a way to compare the expected result array to the superset of the full result array?

The idea here is to end up with something generic that I can feed two arrays (or JSON strings) of arbitrary size/depth, and just verify if the fields of interest in the expected results are in the actual results (with the expected values).

    Are the value comparisons just literal, or might they depend on the keys involved? Are there JSON arrays or just objects?

    I'd be looking at something that uses array_intersect_key (and maybe ksort to canonicalise that part) to trim away the extra fluff from the incoming array (yeah, json_decode first) and then do a mainly direct comparison. Mainly, because it would recurse on nested arrays.

      There will be a mix of arrays and objects in most of them. I was figuring I'd json_decode() them with the 2nd param to true so that everything is an array. Then I started trying to imagine some way to "easily" check specific elements and started semi-visualizing some recursive function, and got a headache. It would be key-centric, I think. I might want to check that $json_result['result_code'] == 'S' and that $json_result['questions'][0]['title'] == 'Overall Experience'.

      As I'm typing this, I'm suddenly thinking of something kind of ugly with the expected results being an array of "keys" and values, where the "keys" would be a string I could use in an eval() statement to specify the part of the result array I want to look at. Uh-oh, another headache coming on. (I'm probably over-thinking this or missing something obvious -- at least I fixed the production bug today that stymied me yesterday.)

      NogDog key-centric

      Yes. You would need to 'extract' the keys (and values) from both the expected results and the data and produce composite keys - 'questions|0|title'. You can then compare the composite keys and values to find if the data matches the expected results.

        (I meant to say array_intersect_key, not array_diff_key in my earlier post. I've corrected that there.)

        function match_pattern(array $incoming, array $pattern): bool
        {
        	// $incoming may have elements not in $pattern
        	$incoming = array_intersect_key($incoming, $pattern);
        
        // But must have every element that is in $pattern
        if(count($incoming) < count($pattern))
        {
        	return false;
        }
        
        ksort($incoming);
        ksort($pattern);
        
        // Might get lucky.
        if($incoming === $pattern)
        {
        	return true;
        }
        
        foreach($pattern as $k => $val)
        {
        	if(is_array($val))
        	{
        		if(!is_array($incoming[$k]) || !match_pattern($incoming[$k], $val))
        		{
        			return false;
        		}
        	}
        	elseif($val !== $incoming[$k])
        	{
        		return false;
        	}
        }
        return true;
        }
        

        Weedpacket
        Thanks for providing that. May not get a chance to really look at it for a bit (latest new priority at work 🙂 ), but definitely will dive in at some point.

          Sweet. Stuck it into my JSON_API class, made just a couple naming tweaks, and seems to work. (Now I need to test the test. 🙂 )

          # php questiontextws_test.php
          Testing questiontextws endpoint.
          Default request:
          RESULT:
          true
          Passed: 3, Failed: 0
          Facility:
          RESULT:
          true
          Passed: 3, Failed: 0
          

            Glad to help. It could be simplified a bit, but I automatically added a couple of optimisations out of habit. I didn't really test to see if they were worthwhile.
            1) The array_intersect_key isn't really necessary, if an array_key_exists test is put inside the loop (return false if $incoming[$k] doesn't exist)
            2) $incoming===$pattern test requires ksort first, but both can be dropped; it's really mostly effective at the deepest level of the nested array.

            Both optimisations involve creating new ($pattern-sized) arrays but I don't expect your patterns are really that large.

            Weedpacket I don't expect your patterns are really that large.

            At this point the arrays are not large (for an undefined value of large), and performance seems fine. There are really only a couple of endpoints where I can imagine the result set being something I might call "large" (e.g. there might be a JSON array with hundreds of objects). Maybe at that point I'll want to bypass those optimizations (perhaps an additional optional $optimize=true argument and an if block to contain them).

            Again, thanks for your help in the fun of working with legacy code written in a custom, in-house framework. 🙂

              7 days later

              Just thought I'd follow up with my current implementation, the only material difference being the addition of the ability to have an expected value of "EXISTS" cause it to just verify that result element is populated, not caring about the actual value.

              private function matchPattern(array $incoming, array $pattern)
              {
                // $incoming may have elements not in $pattern
                $incoming = array_intersect_key($incoming, $pattern);
              
                // But must have every element that is in $pattern
                if (count($incoming) < count($pattern)) {
                  return false;
                }
              
                ksort($incoming);
                ksort($pattern);
              
                // Might get lucky.
                if ($incoming === $pattern) {
                  return true;
                }
              
                foreach ($pattern as $k => $val) {
                  if (is_array($val)) {
                    if (!is_array($incoming[$k]) || !$this->matchPattern($incoming[$k], $val)) {
                      return false;
                    }
                  } elseif($val === 'EXISTS') {
                    return(!empty($incoming[$k]));
                  } elseif ($val !== $incoming[$k]) {
                    return false;
                  }
                }
                return true;
              }
              
                Write a Reply...