Correct way to split nested brackets in PHP, e.g. "root[one[a,b,c],two[d,e,f]]" into array?


Keywords:php 


Question: 

Like the title says it I'm looking for a robust way to parse this expression into an array (example in code).

<?php

$str = 'root[one[a,b,c],two[d,e,f]]'
$decoded = decode($str);

Output:

$decoded = ['root' => ['one' => ['a', 'b', 'c'], 'two' => ['d', 'e', 'f']]

I'm trying to do it via Regex but it since the brackets and commas are nested it's not yielding the correct results. How can I do this?


2 Answers: 

A simple parser below should do the job. I did not test it much - only a successful route, and few obvious edge cases based on fair assumptions.

var_dump(parse('root[one[a,b,c],two[d,e,f]]'));

function parse($str, $level = 0) {
    if ($level === 0) {
        // strip spaces once for simplicity
        $str = str_replace([" ", "\t"], '', $str);
    }
    $result = [];
    while(true) {
        $rest = strpbrk($str, '[],');
        if (!$rest) {
            if (strlen($str) > 0) {
                throw new ExpectedEndOfStringException($str);
            }
            return $result;
        }
        $term = substr($rest, 0, 1);
        $rest = substr($rest, 1);
        $val = substr($str, 0, strpos($str, $term));
        switch ($term) {
            case '[':
                if (!$val) {
                    $val = 0;
                } elseif (isset($result[$val])) {
                    throw new NonUniqueKeyException($val);
                }
                list($a, $str) = parse($rest, $level+1);
                if (is_null($a) && is_null($str)) {
                    throw new BracketsMismatchException($rest);
                }
                $result = array_merge($result, [$val => $a]);
                break;
            case ']':
                if ($val) {
                    $result[] = $val;
                }
                if($level < 1) {
                    throw new BracketsMismatchException($rest);
                }
                if (strlen($rest) > 0 && !in_array(substr($rest, 0, 1), [',', ']'])) {
                    throw new MalformedStringException($val.$term.$rest);
                }
                return [$result, ltrim($rest, ',')];
            case ',':
                if (!$val) {
                    throw new UnexpectedComaException($term.$rest);
                }
                $result[] = $val;
                $str = $rest;
                break;
            default:
                throw new LogicException();
        }
    }
}

You will need to implement all the exception classes, but it should be trivial. Otherwise just replace them with generic \Exception();

 

Based on suggestions by @Rizier123, here it the code I was able to create

<?php

$str = 'root[one[a,b,c],two[d,e,f]]';
$str = preg_replace('/\[/', '=> [', $str);
$str = preg_replace('/(\w+)/', "'\\1'", $str);

eval("\$code = [$str];");
print "<pre>"; print_r($code); exit;

And it seems to be working! Thanks for the help.

Output: