1

I am trying to print any JSON object as simple HTML form inputs with the name properties as the Xpath of the json property in dot notation.

So far it works but i loose track of the initial

    function printInp($title, $pre, $val, $rulekey = false) {
        $html = "<label for='$pre'>$pre</label>";
        $html .= "<input name='$pre' value='$val' type='text' />";
        return $html;
    }


    function printForm($title, $item, $pre = '', $rulekey=false) {
        if (!is_array($item)) {
            echo printInp($title, $pre, $item, $rulekey);
        } else {
            foreach ($item as $key => $val) {
                $pre = (empty($pre)) ? $key : $pre . '.' . $key;                    
                if (is_array($val)) {
                    printForm($title, $val, $pre, $key);
                } else { 
                    echo printInp($title, $pre, $val, $key);
                }
                $pre = '';
            }
        }
    }

    $jsonStr = json_decode('[{"id":12,"name":"live music","icon":{"url":null,"thumb":{"url":null},"ios_profile":{"url":null},"medium":{"url":null}},"display_name":null,"top_level":false}]', TRUE);
    echo printForm('test', $jsonStr);

PRINTS...

    <label for='id'>id</label><input name='id' value='12' type='text' class='' />
    <label for='name'>name</label><input name='name' value='live music' type='text' class='' />
    <label for='icon.url'>icon.url</label><input name='icon.url' value='' type='text' class='' />
    <label for='thumb.url'>thumb.url</label><input name='thumb.url' value='' type='text' class='' />
    <label for='ios_profile.url'>ios_profile.url</label><input name='ios_profile.url' value='' type='text' class='' />
    <label for='medium.url'>medium.url</label><input name='medium.url' value='' type='text' class='' />
    <label for='display_name'>display_name</label><input name='display_name' value='' type='text' class='' />
    <label for='top_level'>top_level</label><input name='top_level' value='' type='text' class='' />

PROBLEM BEING: I loose track of top level node names - "icon" in this case - as i traverse the array.

It should look like this:

    <label for='id'>id</label><input name='id' value='12' type='text' class='' />
    <label for='name'>name</label><input name='name' value='live music' type='text' class='' />
    <label for='icon.url'>icon.url</label><input name='icon.url' value='' type='text' class='' />
    <label for='icon.thumb.url'>icon.thumb.url</label><input name='icon.thumb.url' value='' type='text' class='' />
    <label for='icon.ios_profile.url'>icon.ios_profile.url</label><input name='icon.ios_profile.url' value='' type='text' class='' />
    <label for='icon.medium.url'>icon.medium.url</label><input name='icon.medium.url' value='' type='text' class='' />
    <label for='display_name'>display_name</label><input name='display_name' value='' type='text' class='' />
    <label for='top_level'>top_level</label><input name='top_level' value='' type='text' class='' />

(i will accept answers in any language)

E.A.T
  • 848
  • 11
  • 24
  • if your json object levels are going to be infinite, them you might need to traverse them recursively, if the levels are going to be fixed then just check if that current level is still an array – Kevin Oct 20 '14 at 00:38
  • @Ghost, the levels can go up to 4 levels deep (but i'd rather not design around that limit). i am currently doing it recursively. – E.A.T Oct 20 '14 at 02:31

1 Answers1

3

This answer will be for PHP. As @Ghost had already commented and you confirmed, this needs to be solved with recursion. However, recursion can be somewhat cumbersome in your scenario.

So what you can do in PHP is to turn the recursion into your flat list of input elements by collaborating with RecursiveIteratorIterator easily.

Let's start with a first, simple example which still is incomplete but shows how it works. This and the following examples will make use of your JSON string, but I name the variables a little differently:

$jsonStr
    = <<<JSON
[
    {
        "id"  : 12,
        "name": "live music",
        "icon": {
                    "url"        : null,
                    "thumb"      : {"url": null},
                    "ios_profile": {"url": null},
                    "medium"     : {"url": null}
                },
        "display_name": null,
        "top_level"   : false
    }
]
JSON;
$json = json_decode($jsonStr, true);

So the first example showing how to make use of RecursiveIteratorIterator here to turn your recursive tree structure you have in the JSON string into a flat list:

$it   = new RecursiveArrayIterator($json);
$rit  = new RecursiveIteratorIterator($it);
foreach ($rit as $key => $value) {
    printf("%s: %s\n", $key, var_export($value, 1));
}

This outputs all leaf-nodes of that tree with key and value one after the other:

id: 12
name: 'live music'
url: NULL
url: NULL
url: NULL
url: NULL
display_name: NULL
top_level: false

It still does not have the path to each node what you're looking for as well. But it already shows, that the tree has been turned into a list.

To now obtain the path to each node, RecursiveIteratorIterator allows to traverse all keys up to the current node's iterator through its getSubIterator method. Taking depth information into account, the path can be easily created:

foreach ($rit as $key => $value) {
    $jsonPathSegments = [];
    for ($level = 0; $level <= $rit->getDepth(); $level++) {
        $jsonPathSegments[$level] = $rit->getSubIterator($level)->key();
    }
    $jsonPath = implode('.', $jsonPathSegments);

    printf("%s: %s\n", $jsonPath, var_export($value, 1));
}

This now produces the following output:

0.id: 12
0.name: 'live music'
0.icon.url: NULL
0.icon.thumb.url: NULL
0.icon.ios_profile.url: NULL
0.icon.medium.url: NULL
0.display_name: NULL
0.top_level: false

which now shows, that obtaining the path to each node is easily possible. This still needs some fine-tuning as you actually don't want the key of the first (0) level. This can be done by initializing $level to 1 in the for loop:

    for ($level = 1; $level <= $rit->getDepth(); $level++) {
                  ^

Updated output:

id: 12
name: 'live music'
icon.url: NULL
icon.thumb.url: NULL
icon.ios_profile.url: NULL
...

so this solves the underlying issue already but also allows you to care about the output later. Wrapping this up and exploiting __toString even allows you to easily create a HTML widget:

/**
 * Class JsonForm
 * 
 * Exemplary HTML Widget
 */
class JsonForm extends RecursiveIteratorIterator
{
    private $json;
    public function __construct($json) {
        if (is_string($json) || is_object($json) && method_exists($json, '__toString')) {
            $json = json_decode($json);
        }
        parent::__construct(new RecursiveArrayIterator($json));
    }

    public function getJsonPath() {
        $jsonPathSegments = [];
        for ($level = 1; $level <= $this->getDepth(); $level++) {
            $jsonPathSegments[$level] = $this->getSubIterator($level)->key();
        }
        $jsonPath = implode('.', $jsonPathSegments);

        return $jsonPath;
    }

    public function __toString() {
        $html = '';
        foreach ($this as $value) {
            $path = $this->getJsonPath();
            $html .= sprintf(
                '<label for="%1$s">%1$s</label><input name="%1$s" value="%2$s" type="text" />'."\n",
                htmlspecialchars($path), htmlspecialchars($value)
            );
        }
        return $html;
    }
}

This encapsulates or the needed processing logic in a simple call:

echo new JsonForm($jsonStr);

producing the following exemplary output:

<label for="id">id</label><input name="id" value="12" type="text" />
<label for="name">name</label><input name="name" value="live music" type="text" />
<label for="icon.url">icon.url</label><input name="icon.url" value="" type="text" />
<label for="icon.thumb.url">icon.thumb.url</label><input name="icon.thumb.url" value="" type="text" />
<label for="icon.ios_profile.url">icon.ios_profile.url</label><input name="icon.ios_profile.url" value="" type="text" />
<label for="icon.medium.url">icon.medium.url</label><input name="icon.medium.url" value="" type="text" />
<label for="display_name">display_name</label><input name="display_name" value="" type="text" />
<label for="top_level">top_level</label><input name="top_level" value="" type="text" />

I hope this gives some pointers. It shows how you can treat the tree structure as a flat list thanks to the operation of RecursiveIteratorIterator in PHP (reference: How does RecursiveIteratorIterator work in PHP?).

The building of the path in a recursive structure based on subkeys has been previously outlined in

. Most likely there are more examples of this.

Community
  • 1
  • 1
hakre
  • 193,403
  • 52
  • 435
  • 836