将多维JSON打印为html表单输入,名称为属性的xpath


printing multi-dimensional JSON as html form inputs with name as xpath of property

我正试图将任何JSON对象打印为简单的HTML表单输入,并将名称属性作为JSON属性的Xpath,用点表示。

到目前为止,它是有效的,但我失去了最初的跟踪

    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);

打印。。。

    <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='' />

问题是:当我遍历数组时,我失去了对顶级节点名称(在本例中为"图标")的跟踪。

应该是这样的

    <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='' />

(我将接受任何语言的答案)

这个答案适用于PHP。正如@Ghost已经评论过的,你也确认了,这需要用递归来解决。然而,在您的场景中,递归可能会有些麻烦。

因此,在PHP中,您可以通过与RecursiveIteratorIterator轻松协作,将递归转换为输入元素的平面列表。

让我们从第一个简单的例子开始,这个例子仍然不完整,但展示了它是如何工作的。这个和下面的例子将使用JSON字符串,但我对变量的命名有点不同:

$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);

因此,第一个示例显示了如何使用RecursiveIteratorIterator将JSON字符串中的递归树结构转换为平面列表:

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

这会一个接一个地输出该树的所有叶节点以及关键字和值:

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

它仍然没有您所要查找的每个节点的路径。但它已经表明,这棵树已经变成了一个列表。

现在要获得每个节点的路径,RecursiveIteratorIterator允许通过其getSubIterator方法遍历所有键,直到当前节点的迭代器。考虑到深度信息,可以很容易地创建路径:

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));
}

这现在产生以下输出:

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

现在表明获得到每个节点的路径是容易实现的。这仍然需要一些微调,因为你实际上不想要第一个(0)级别的键。这可以通过在for循环中将$level初始化为1来实现:

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

更新输出:

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

因此,这已经解决了的根本问题,但也允许您稍后关心输出。将其封装起来并利用__toString甚至可以轻松地创建一个HTML小部件:

/**
 * 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;
    }
}

这将所需的处理逻辑封装在一个简单的调用中:

echo new JsonForm($jsonStr);

产生以下示例性输出:

<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" />

我希望这能给我们一些启示。它展示了如何通过在PHP中操作RecursiveIteratorIterator将树结构视为平面列表(参考:RecursiveITeratorItator如何在PHP中工作?)。

基于子密钥的递归结构中的路径构建已在中进行了概述

  • PHP-从面包屑列表的多维关联数组返回父数组(2012年12月)
  • 按值获取所有数组键(2013年2月)或
  • 递归获取数组的键并创建下划线分隔的字符串(2010年5月)

很可能还有更多这样的例子。