遍历深度嵌套的JSON对象 [英] Traversing through deeply nested JSON object

查看:79
本文介绍了遍历深度嵌套的JSON对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

与用于构建表单的API进行交互时,我进行API调用以获取与我的表单相关的所有响应值.该API返回具有我所有表单值的深度嵌套的JSON对象.

When interacting with an API used for building forms, I make an API call to get all the response values associated with my form. The API returns a deeply nested JSON object with all my form values.

许多响应对象之一如下:

One of the many response objects looks like this:

{
      "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,将其缩减为一个数组?

I'm thinking I'll have to use some sort of map-reduce algorithm because simply nesting a bunch of loops can be unscalable as well as computationally expensive given the increasing time complexity if its a large dataset. Maybe I have to write a recursive function that maps through my dataset, checks the id value, reduces it down to an array if it finds a matching id?

此外,我想避免使用第三方库.PHP具有足够的本机功能来简化我要完成的工作.

Additionally, I'd like to avoid using a 3rd party library. PHP has enough native functions to facilitate what I'm trying to accomplish.

推荐答案

实际上,不需要魔术算法.只需一点entent,水合器和过滤器形式的php魔术.

Actually there 's no need for a magic algorithm. Just a bit of php magic in form of entites, hydrators and filters.

在此答案中,您将获得一种面向对象的php方法,该方法会将json api响应合并为对象,您可以轻松对其进行过滤.请记住,在此oop方法中,所有东西都是对象.

In this answer you 'll get an object orientated php approach, which will hydrate the json api response into objects, which you can easily filter. Just keep in mind, that in this oop apporach everything is an object.

数据对象-数据实体

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

First of all you have to know, how your data is structured. From this structury you can build php objects. From the given JSON structure you can use the following objects.

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对象的属性.为什么此类实现 JsonSerializable接口?通过此实现,您可以将类结构转换回结构良好的json字符串.我不确定,是否需要.但是可以肯定的是,与其他api通信时,它是安全的.您现在唯一要做的就是为每个预期的复杂数据结构/json对象编程实体.您需要具有plin属性的title对象和具有label和fieldset属性的fieldset对象,等等.

Well, this class depicts the properties of the very first json object given in your example. Why this class implements the JsonSerializable interface? With this implementation you are able to transform the class structure back into a well formed json string. I 'm not sure, if you need that. But for sure it is safe, while communicating with a rest api. The only thing you have to do now is programming entities for every expected complex data structure / json object. You need the title object with a plin property and a fieldset object with label and fieldset properties and so on.

如何将json数据导入php对象-水化

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

Of course, your given json structure is a string. When we talk about hydration we actually mean converting a json string into an object structure. The above mentioned entites are needed for this approach.

但是首先是水合器类本身.

But first the hydrator class itself.

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数据复杂且嵌套,因此我们必须使用水化器策略.水合作用的常见模式.可以将策略挂接到对象属性中并执行另一个水化器.因此,我们确保以相同的对象结构表示嵌套数据.

Well, this is the class where all the magic happens. I guess this is what you mentioned as algorithm. The hydrator takes your data from the json response and pushes it into your entities. When having the entites hydrated, you can easily access the given data by calling the get methods of the entities. As the json data is complex and nested, we have to use hydrator strategies. A common pattern in hydration concerns. A strategy can be hooked into a object property and executes another hydrator. So we make sure that we represent the nested data in an identical object structure.

这是水合策略的一个例子.

Here 's an example of a hydrator strategy.

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响应时,存在严重元素,这些元素是一个对象或包含对象.为了正确地水合这种多维结构,我们使用策略.

So what a hydrator strategy actually does? While iterating over our json api response, there are severel elements, which are an object or contain objects. To hydrate this multidimensional structure correctly, we use strategies.

为了与您的JSON响应示例保持一致,我添加了一个简单的用例.首先,我们将json响应解码为一个关联的多维数组.之后,我们使用实体,水合器和水合器策略来获取包含所有数据的对象.用例知道,JSON响应中的title属性是一个对象,应合并到我们的title实体中,该title实体包含plain属性.

To stay with your example of the JSON response I 've added a simple use case. First we decode the json response into an assiciative, multidimensional array. After that we use our entities, hydrators and hydrator strategies to get an object, which contains all the data. The use case knows, that the title property in the JSON response is an object that should hydrated into our title entity, which contains the plain property.

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

At the end our hydrated object has a structure like this ...

\Application\Entity\Section {
     public:title => \Application\Entity\Title [
         public:plain => string 'Send Money'
     }
     ...
}

实际上,您可以使用我们实体的getter方法访问属性.

Actually you can access the properties with the getter methods of our entities.

echo $section->getTitle()->getPlain(); // echoes 'Send money'

知道如何补水我们的课程,将我们带入下一步.聚集!

Knowing how to hydrate our classes leads us to the next step. Aggregation!

通过聚合获取完整字符串

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

Actually aggregation is a common design pattern in modern object orientated programming. Aggregation means no more and no less than the allocation of data. Let 's have a look at your posted JSON response. As we can see the fieldset property of our root object contains a collection of fieldset objects, that we can access via our getter and setter methods. With this in mind, we can create additional getter methods in our section entity. Let us expand our section entity with a getFullName method.

...
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 方法只是一个小帮手,它用填充符(空格)将许多字符串连接起来.

This example assumes, that both the first name and the last name are available in the very first item of the fieldset collection of the section entity. So we get Bob Hogan as return value. The concatenate method is just a little helper, which concatenates a number of strings with a filler (space).

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

您还提到过,您必须按ID查找特定数据.一种可能的解决方案可能是使用过滤器迭代器通过特定项过滤我们的实体.课.

Further you mentioned, that you have to find specific data by id. One possible solution coould be filtering our entities by a specific item with the Filter Iterator class.

namespace Application\Filter;

class PersonIdFilter extends \FilterIterator
{
    protected $id;

    public function __construct(Iterator $iterator, string $id)
    {
        parent::__construct($iterator);
        $this->id = $id;
    }

    public function accept()
    {
        $person = $this->getInnerIterator()->current();
        return ($person->getId() == $this->id) ? true : false;
    }
}

由于在我们的集合中使用了 ArrayObject 类,因此我们能够使用迭代器来过滤特定的参数.在这种情况下,我们会在我们的个人信息字段集中过滤ID.

Because of using ArrayObject classes for our collections we are able to use iterators to filter for a specific argument. In this case we filter for the id in our personal info fieldsets.

从水化示例开始,我们可能像下面的代码一样.

Starting from our hydration example we it could be something like the following code.

$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响应并返回所需的数据,这是有道理的.由于可伸缩性高,因此在这种情况下使用对象更加有意义.您可以通过调用正确的getter方法来快速浏览所需的所有数据.此外,代码比庞大的函数更具可读性,而后者又一次又一次地迭代.在上面显示的方法中,您只需编写一次代码,然后一次又一次地重复使用所有对象.

As you said you want a scalable solution without nested iterations in a huge loop. In my eyes it makes sense not writing just a huge single function, wich iterates the json response and returns the data you want. Working with objects in this case makes mch more sense because of the high scalability. You can access all the data you want in a glimpse by calling the right getter methods. Furthermore the code is mich more readable than a huge function which is recursivly iterating again and again. In the above shown approach you only code once and reuse all the objects again and again.

请记住,上面显示的代码只是理论上的建议.未经测试.

Please keep in mind, the the above shown code is just a theoretical suggestion. It is not tested.

这篇关于遍历深度嵌套的JSON对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆