遍历深度嵌套的JSON对象

遍历深度嵌套的JSON对象,json,mapreduce,associative-array,php-7,Json,Mapreduce,Associative Array,Php 7,当与用于构建表单的API交互时,我进行API调用以获取与表单关联的所有响应值。API返回一个包含所有my form值的深度嵌套JSON对象 许多响应对象中的一个如下所示: { "title":{ "plain":"Send Money" }, "fieldset":[ { "label":{ "plain":"Personal Info Section" },

当与用于构建表单的API交互时,我进行API调用以获取与表单关联的所有响应值。API返回一个包含所有my form值的深度嵌套JSON对象

许多响应对象中的一个如下所示:

{
      "title":{
        "plain":"Send Money"
      },
      "fieldset":[
        {
          "label":{
            "plain":"Personal Info Section"
          },
          "fieldset":[
            {
              "field":[
                {
                  "label":{
                    "plain":"First Name"
                  },
                  "value":{
                    "plain":"Bob"
                  },
                  "id":"a_1"
                },
                {
                  "label":{
                    "plain":"Last Name"
                  },
                  "value":{
                    "plain":"Hogan"
                  },
                  "id":"a_2"
                }
              ],
              "id":"a_8"
            }
          ],
          "id":"a_5"
        },
        {
          "label":{
            "plain":"Billing Details Section"
          },
          "fieldset":{
            "field":{
              "choices":{
                "choice":{
                  "label":{
                    "plain":"Gift"
                  },
                  "id":"a_17",
                  "switch":""
                }
              },
              "label":{
                "plain":"Choose a category:"
              },
              "value":{
                "plain":"Gift"
              },
              "id":"a_14"
            },
            "fieldset":{
              "label":{
                "plain":""
              },
              "field":[
                {
                  "choices":{
                    "choice":{
                      "label":{
                        "plain":"Other"
                      },
                      "id":"a_25",
                      "switch":""
                    }
                  },
                  "label":{
                    "plain":"Amount"
                  },
                  "value":{
                    "plain":"Other" //(This could also be a dollar amount like 10.00)
                  },
                  "id":"a_21"
                },
                {
                  "label":{
                    "plain":"Other Amount"
                  },
                  "value":{
                    "plain":"200"
                  },
                  "id":"a_20"
                }
              ],
              "id":"a_26"
            },
            "id":"a_13"
          },
          "id":"a_12"
        }
      ]
    }
这里的目标是运行所有响应的报告,并以可读的方式打印数据(例如,“Bob Hogan-$200,Chad Smith-$100”)。

我想我必须使用某种map reduce算法,因为如果是一个大数据集,那么简单地嵌套一堆循环是不可伸缩的,并且由于时间复杂性的增加,计算成本也会增加。也许我必须编写一个递归函数,通过我的数据集进行映射,检查id值,如果找到匹配的id,则将其缩减为一个数组


此外,我希望避免使用第三方库。PHP有足够的本机函数来帮助我完成所要完成的任务。

实际上,不需要神奇的算法。仅仅是一点php的魔力,包括实体、水合器和过滤器

在这个答案中,您将得到一种面向对象的php方法,它将json api响应水合为对象,您可以轻松地过滤这些对象。请记住,在这个面向对象的方法中,一切都是一个对象

数据对象-数据实体

首先,您必须知道数据的结构。从这个结构中,您可以构建php对象。从给定的JSON结构中,您可以使用以下对象

namespace Application\Entity;

// Just for recognizing entities as entities later
interface EntityInterface
{

}

class Title implements EntityInterface, \JsonSerializable
{
    public $plain;

    public function getPlain() : ?string
    {
        return $this->plain;
    }

    public function setPlain(string $plain) : Title
    {
        $this->plain = $plain;
        return $this;
    }

    public function jsonSerialize() : array
    {
        return get_object_vars($this);
    }
}

class Fieldset implements EntityInterface, \JsonSerializable
{
    /**
     * Label object
     * @var Label
     */
    public $label;

    /**
     * Collection of Field objects
     * @var \ArrayObject
     */
    public $fieldset;

    // implement getter and setter methods here
}

class Section implements EntityInterface, \JsonSerializable
{
    public $title;

    public $fieldsets;

    public function getTitle() : ?Title
    {
        return $this->title;
    }

    public function setTitle(Title $title) : Section
    {
        $this->title = $title;
        return $this;
    }

    public function getFieldsets() : \ArrayObject
    {
        if (!$this->fieldsets) {
            $this->fieldsets = new \ArrayObject();
        }

        return $this->fieldsets;
    }

    public function setFieldsets(Fieldset $fieldset) : Section
    {
        if (!$this->fieldsets) {
            $this->fieldsets = new \ArrayObject();
        }

        $this->fieldsets->append($fieldset);
        return $this;
    }

    public function jsonSerialize() : array
    {
         return get_object_vars($this);
    }
}
这个类描述了示例中给出的第一个json对象的属性。为什么这个类实现了?通过这个实现,您可以将类结构转换回格式良好的json字符串。我不确定,如果你需要的话。但可以肯定的是,在与RESTAPI通信时,它是安全的。现在唯一需要做的就是为每个预期的复杂数据结构/json对象编写实体。您需要具有plin属性的title对象和具有label和fieldset属性的fieldset对象等

如何将json数据获取到php对象中

当然,给定的json结构是一个字符串。当我们谈论水合作用时,实际上是指将json字符串转换为对象结构。这种方法需要上述实体

但首先是水合器类本身

namespace Application\Hydrator;
use \Application\Entity\EntityInterface;

class ClassMethodsHydrator
{
    protected $strategies;

    public function hydrate(array $data, EntityInterface $entity) : EntityInterface
    {
        foreach ($data as $key => $value) {
            if (!method_exists($entity, 'set' . ucfirst($key)) {
                throw new \InvalidArgumentException(sprintf(
                    'The method %s does not exist in %s',
                    get_class($entity)
                ));
            }

            if ($this->strategies[$key]) {
                $strategy = $this->strategies[$key];
                $value = $strategy->hydrate($value);
            }

            $entity->{'set' . ucfirst($key)}($value);
        }

        return $entity;
    }

    public function addStrategy(string $name, StrategyInterface $strategy) : Hydrator
    {
        $this->strategies[$name] = $strategy;
        return $this;
    }
}
好吧,这是一节魔法发生的课。我猜这就是你提到的算法。浏览器从json响应中获取数据并将其推送到实体中。将实体水合后,可以通过调用实体的get方法轻松访问给定数据。 由于json数据是复杂的嵌套数据,我们必须使用XML或策略。水合作用的一种常见模式。一个策略可以连接到一个对象属性并执行另一个对象。因此,我们确保在相同的对象结构中表示嵌套数据

下面是一个水合器策略的示例

namespace Application\Hydrator\Strategy;
use \Application\Entity\EntityInterface;

interface HydratorStrategy
{
    public function hydrate(array $value) : EntityInterface;
}

use \Application\Entity\Title;
class TitleHydratorStrategy implements HydratorStrategy
{
    public function hydrate(array $value) : EntityInterface
    {
        $value = (new ClassMethods())->hydrate($value, new Title);
        return $value;
    }
}

// Use case of a strategy
$data = json_decode$($response, true);
$section = (new ClassMethods())
    ->addStrategy('title', new TitleHydratorStrategy())
    ->hydrate($data, new Section());
那么,水合器策略实际上是做什么的呢?在迭代json api响应时,有几个元素,它们是对象或包含对象。为了正确地水合这个多维结构,我们使用策略

为了继续您的JSON响应示例,我添加了一个简单的用例。首先,我们将json响应解码为一个辅助的多维数组。之后,我们使用实体、水合器和水合器策略来获得一个包含所有数据的对象。用例知道,JSON响应中的title属性是一个对象,应该包含在我们的title实体中,该实体包含plain属性

最后我们的水合物体有这样的结构

\Application\Entity\Section {
     public:title => \Application\Entity\Title [
         public:plain => string 'Send Money'
     }
     ...
}
实际上,您可以使用实体的getter方法访问属性

echo $section->getTitle()->getPlain(); // echoes 'Send money'
知道如何给我们的课程补充水分会让我们进入下一步。聚合

通过聚合获取完整字符串

实际上,聚合是现代面向对象编程中一种常见的设计模式。聚合意味着不多于也不少于数据分配。让我们看看您发布的JSON响应。正如我们所看到的,根对象的fieldset属性包含一组fieldset对象,我们可以通过getter和setter方法访问这些对象。考虑到这一点,我们可以在节实体中创建其他getter方法。让我们用
getFullName
方法扩展节实体

...
public function getFullName() : string
{
    $firstname = $lastname = '';

    // fetch the personal info section
    if ($this->getFieldsets()->offsetExists(0)) {
         $personalInfoFieldset = $this->getFieldsets()->offsetGet(0)->getFiedlset()->offsetGet(0);
         $firstname = $personalInfoFieldset->getField()->offsetGet(0)->getValue();
         $lastname = $personalInfoFieldset->getField()->offsetGet(1)->getValue();
    }

    return $this->concatenate(' ', $firstname, $lastname);
}

public function concatenate(string $filler, ...$strings) : string
{
    $string = '';
    foreach ($strings as $partial) {
        $string .= $partial . $filler;
    }

    return trim($string);
}
本例假设在节实体的字段集集合的第一项中,名和姓都可用。因此,我们得到
Bob Hogan
作为返回值。
concatenate
方法只是一个小助手,它用一个填充符(空格)连接多个字符串

使用我们的实体和FilterEditor类过滤数据

此外,您还提到,您必须按id查找特定数据。一种可能的解决方案是使用类按特定项筛选实体

由于为集合使用了
ArrayObject
类,我们可以使用迭代器过滤特定参数。在本例中,我们在个人信息字段集中筛选id

从我们的水合示例开始,我们可以使用如下代码

$personalIterator = $section->getFieldsets()->offsetGet(0)->getFieldset()->getIterator();
$filter = new PersonIdFilter($personalIterator, 'a_8');
foreach ($filter as $result) {
    var_dump($result); // will output the first fieldset with the personal data
}
太复杂了?绝对不是

正如您所说的,您需要一个可伸缩的解决方案,而不需要在一个巨大的循环中嵌套迭代。在我看来,编写一个庞大的函数是有意义的,它迭代json响应并返回所需的数据。在这种情况下,使用对象使mch更有意义,因为它具有很高的可伸缩性。通过调用正确的getter方法,您可以在一瞥中访问所有需要的数据。此外,该代码比递归的大型函数可读性更好
$personalIterator = $section->getFieldsets()->offsetGet(0)->getFieldset()->getIterator();
$filter = new PersonIdFilter($personalIterator, 'a_8');
foreach ($filter as $result) {
    var_dump($result); // will output the first fieldset with the personal data
}