Talisin
Talisin

Reputation: 594

Compare multidimensional array against mandatory values

I have a multidimensional array and need to verify if the array includes the mandatory fields on the correct layer.

Is there a good approche to achieve that? The input can be an array or a JSON.

My approche so far:

Lets assume the input array is the following:

 Array (
    [key] => key string
    [sub] => Array (
        [0] => Array (
            [id] => 123456
            [src] => source string
            [src_ids] => Array (
                [0] => 1
                [1] => 2
                [2] => 3
                [3] => 4
            )
            [data] => Array (
                [a_data] => a string
                [b_data] => b string
                [c_data] => c string
            )
        )
        [1] => Array (
            [id] => 557799
            [src] => source string
            [src_ids] => Array (
                [0] => 1
                [1] => 2
            )
            [data] => Array (
                [a_data] => a string
                [b_data] => b string
                [c_data] => c string
            )
        )
    )
)

The mandatory values are:

sub, id, src, src_ids, data, a_data

Optional values are everything else like:

key, 0 (of sub), 0-3 (of src_ids), b_data, c_data

I would like to have a function which checks any input array against the mandatory fields. My idea was to create the mandatory values as an array based in layers like:

$mandatory = array( 
    1 => array( 'sub' ),
    3 => array( 'id', 'src', 'src_id', 'data' ),
    4 => array( 'a_data' )
);

The key is the depth of the layer and the array value is the allowed key names for that layer.

Now I try to crawl through the input array and check if the key is in the mandatory field based on the depth:

/**
 * 
 * @param array $i input
 * @param array $m mandatory
 * @param integer $d depth
 * @return boolean
 */
function validate( $i, $m, $d=1 ) {
    foreach( $i as $k => $v ) {
        if( isset($m[$d]) ) {
            if( !in_array($k, $m[$d]) ) {
                return false;
            } else {
                if( is_array($v) ) {
                    $d++;
                    validate($v, $m, $d );
                }
            }
        } else {
            if( is_array($v) ) {
                $d++;
                validate($v, $m, $d );
            }
        }
    }
    return true;
}

But this will cause two major issues:

  1. layer number 4 holds the array of src_ids and data but only in data there is the mandatory field a_data. So it will return false for layer number 4
  2. but the main issue is the logical problem in this function. Right now it don't check the mandatory field if they are set, it only check if the input value of the depth is in the mandatory field. This behavior is wrong.

I also tried to use a comparing array but due to the multiple entries I was not able to compare all the entries against the mandatory array.

Any help, suggestion or solutions are much appreciated.

Thanks

Upvotes: 1

Views: 184

Answers (2)

ghbarratt
ghbarratt

Reputation: 11711

If you define $input and $mandatory as described, and the rest of the code looked like this:

$values[1] = $input;
$layer = 1;
$found_layer = true;

while($found_layer)
{
        $found_layer = false;
        foreach($values[$layer] as $li=>$le)
        {
                if(is_array($le))
                {
                        $found_layer = true;
                        if(!isset($values[$layer+1])) $values[$layer+1] = array();
                        $values[$layer+1] = array_merge($le, $values[$layer+1]);
                        $values[$layer][$li] = 'array';
                }
        }
        $layer++;
}

$not_found = $mandatory;

foreach($not_found as $layer=>$keys_to_match)
{
        foreach($values[$layer] as $key=>$value)
        {
                foreach($keys_to_match as $ktmi=>$ktm)
                {
                        if($ktm==$key) unset($not_found[$layer][$ktmi]);
                }
        }
        if(!count($not_found[$layer])) unset($not_found[$layer]);
        else $not_found[$layer] = array_values($not_found[$layer]);
}

then the code would produce $not_found as the following:

Array
(
    [3] => Array
        (
            [0] => src_id
        )

)

which means, that there is no element on the third level ("layer") of input with the key "src_id" which is mandatory according to $mandatory. If all mandatory elements are found then $not_found will be empty. Note, you have 'src_id' in $mandatory but the $input clearly only has a key 'src_ids'.

I avoided the use of recursion for performance.

Upvotes: 1

Wiseguy
Wiseguy

Reputation: 20873

Does this fulfill your requirements?

Required field structure:

$required = array(
    'sub' => array(
        array(
            'id' => '',
            'src' => '',
            'src_ids' => '',
            'data' => array(
                'a_data' => '',
            ),
        ),
    ),
);

Basically just mimic the structure of the input array. Field names are keys; required subfields are descendants. Leave key numeric/unnamed if it's a list as with sub[0] and sub[1], and it will check structure of each item in the list.

Recursive validation function:

function checkRequired($data, $req)
{
    // no more required fields
    if (empty($req))
        return true;

    // no more data fields; obviously lacks required field(s)
    if (empty($data))
        return false;

    foreach ($req as $name => $subtree) {
        // unnamed; it's a list
        if (is_numeric($name)) {
            foreach ($data as $dataitem) {
                if (checkRequired($dataitem, $subtree) == false)
                    return false;
            }
        } else {
            // required field doesn't exist
            if (!isset($data[$name]))
                return false;

            // fine so far; down we go
            if (!empty($subtree)
                && checkRequired($data[$name], $subtree) == false
            ) {
                return false;
            }
        }
    }

    return true;
}

I likely goofed somewhere, but hopefully you can adapt this into something useful. Here's a working example of this you can experiment with.

Upvotes: 1

Related Questions