遍历深度嵌套的 JSON 对象
Traversing through deeply nested JSON object
在与用于构建表单的 API 交互时,我进行了 API 调用以获取与我的表单关联的所有响应值。 API returns 一个深度嵌套的 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 - 0, Chad Smith - 0")。
我想我将不得不使用某种 map-reduce 算法,因为简单地嵌套一堆循环可能是不可扩展的,而且如果它是一个大数据集,考虑到时间复杂性的增加,计算量也很大。也许我必须编写一个递归函数来映射我的数据集,检查 id 值,如果找到匹配的 id,则将其缩减为数组?
此外,我想避免使用第 3 方库。 PHP 有足够的原生功能来帮助我完成我想要完成的事情。
其实不需要什么神奇的算法。实体、水化器和过滤器形式的 php 魔法。
在这个答案中,你将得到一个 object 导向的 php 方法,它将 json api 响应水化为 objects,这你可以很容易地过滤。请记住,在这个 oop 方法中,一切都是 object.
数据object-数据实体
首先你必须知道你的数据是如何构建的。从这个结构你可以构建 php objects。从给定的 JSON 结构中,您可以使用以下 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);
}
}
好吧,这个 class 描述了您示例中给出的第一个 json object 的属性。为什么这个 class 实现了 JsonSerializable interface?使用此实现,您可以将 class 结构转换回格式正确的 json 字符串。我不确定,如果你需要的话。但是可以肯定的是它是安全的,同时与休息api进行通信。您现在唯一要做的就是为每个预期的复杂数据结构/jsonobject 编写实体。您需要带有 plin 属性 的标题 object 和带有标签和字段集属性的字段集 object 等等。
如何将 json 数据放入 php object - 水合作用
当然,您给定的 json 结构是一个字符串。当我们谈论水合作用时,我们实际上是指将 json 字符串转换为 object 结构。此方法需要上述实体。
但首先是保湿器 class 本身。
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;
}
}
好吧,这就是 class 所有魔法发生的地方。我想这就是你提到的算法。 hydrator 从 json 响应中获取您的数据并将其推送到您的实体中。当实体被水合时,您可以通过调用实体的 get 方法轻松访问给定的数据。
由于 json 数据复杂且嵌套,我们必须使用 hydrator 策略。水合作用问题的常见模式。一个策略可以挂接到 object 属性 并执行另一个水化器。因此,我们确保以相同的 object 结构表示嵌套数据。
这是一个水化策略的例子。
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 响应时,有一些元素,它们是 object 或包含 object。为了正确地水合物这个多维结构,我们使用策略。
为了继续使用您的 JSON 响应示例,我添加了一个简单的用例。首先,我们将 json 响应解码为关联的多维数组。之后,我们使用我们的实体、水化器和水化器策略来获得包含所有数据的 object。用例知道,JSON 响应中的标题 属性 是一个 object,应该合并到我们的标题实体中,其中包含普通的 属性。
最后我们的水合 object 有这样的结构...
\Application\Entity\Section {
public:title => \Application\Entity\Title [
public:plain => string 'Send Money'
}
...
}
实际上,您可以使用我们实体的 getter 方法访问属性。
echo $section->getTitle()->getPlain(); // echoes 'Send money'
了解如何为我们的 class 补水可以让我们进入下一步。聚合!
通过聚合获取完整字符串
实际上,聚合是现代面向 object 的编程中的常见设计模式。聚合意味着不多于也不少于数据分配。让我们看看您发布的 JSON 回复。正如我们所看到的,根 object 的字段集 属性 包含字段集 object 的 collection,我们可以通过 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);
}
此示例假定,名字和姓氏在部分实体的字段集 collection 的第一项中可用。所以我们得到 Bob Hogan
作为 return 值。 concatenate
方法只是一个小帮手,它将多个字符串与填充符 (space) 连接起来。
使用我们的实体和 FilterIterator 过滤数据class
您进一步提到,您必须通过 id 查找特定数据。一种可能的解决方案是使用 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;
}
}
因为我们的 collection 使用 ArrayObject
classes,所以我们能够使用迭代器来过滤特定参数。在这种情况下,我们在个人信息字段集中过滤 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 响应和 return 所需的数据。在 th 中使用 objects由于高可扩展性,s 案例使 mch 更有意义。您可以通过调用正确的 getter 方法一目了然地访问所有您想要的数据。此外,代码比一次又一次地递归迭代的巨大函数更具可读性。在上面显示的方法中,您只需编写一次代码并一次又一次地重复使用所有 object。
请记住,上面显示的代码只是一个理论上的建议。它没有经过测试。
在与用于构建表单的 API 交互时,我进行了 API 调用以获取与我的表单关联的所有响应值。 API returns 一个深度嵌套的 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 - 0, Chad Smith - 0")。
我想我将不得不使用某种 map-reduce 算法,因为简单地嵌套一堆循环可能是不可扩展的,而且如果它是一个大数据集,考虑到时间复杂性的增加,计算量也很大。也许我必须编写一个递归函数来映射我的数据集,检查 id 值,如果找到匹配的 id,则将其缩减为数组?
此外,我想避免使用第 3 方库。 PHP 有足够的原生功能来帮助我完成我想要完成的事情。
其实不需要什么神奇的算法。实体、水化器和过滤器形式的 php 魔法。
在这个答案中,你将得到一个 object 导向的 php 方法,它将 json api 响应水化为 objects,这你可以很容易地过滤。请记住,在这个 oop 方法中,一切都是 object.
数据object-数据实体
首先你必须知道你的数据是如何构建的。从这个结构你可以构建 php objects。从给定的 JSON 结构中,您可以使用以下 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);
}
}
好吧,这个 class 描述了您示例中给出的第一个 json object 的属性。为什么这个 class 实现了 JsonSerializable interface?使用此实现,您可以将 class 结构转换回格式正确的 json 字符串。我不确定,如果你需要的话。但是可以肯定的是它是安全的,同时与休息api进行通信。您现在唯一要做的就是为每个预期的复杂数据结构/jsonobject 编写实体。您需要带有 plin 属性 的标题 object 和带有标签和字段集属性的字段集 object 等等。
如何将 json 数据放入 php object - 水合作用
当然,您给定的 json 结构是一个字符串。当我们谈论水合作用时,我们实际上是指将 json 字符串转换为 object 结构。此方法需要上述实体。
但首先是保湿器 class 本身。
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;
}
}
好吧,这就是 class 所有魔法发生的地方。我想这就是你提到的算法。 hydrator 从 json 响应中获取您的数据并将其推送到您的实体中。当实体被水合时,您可以通过调用实体的 get 方法轻松访问给定的数据。 由于 json 数据复杂且嵌套,我们必须使用 hydrator 策略。水合作用问题的常见模式。一个策略可以挂接到 object 属性 并执行另一个水化器。因此,我们确保以相同的 object 结构表示嵌套数据。
这是一个水化策略的例子。
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 响应时,有一些元素,它们是 object 或包含 object。为了正确地水合物这个多维结构,我们使用策略。
为了继续使用您的 JSON 响应示例,我添加了一个简单的用例。首先,我们将 json 响应解码为关联的多维数组。之后,我们使用我们的实体、水化器和水化器策略来获得包含所有数据的 object。用例知道,JSON 响应中的标题 属性 是一个 object,应该合并到我们的标题实体中,其中包含普通的 属性。
最后我们的水合 object 有这样的结构...
\Application\Entity\Section {
public:title => \Application\Entity\Title [
public:plain => string 'Send Money'
}
...
}
实际上,您可以使用我们实体的 getter 方法访问属性。
echo $section->getTitle()->getPlain(); // echoes 'Send money'
了解如何为我们的 class 补水可以让我们进入下一步。聚合!
通过聚合获取完整字符串
实际上,聚合是现代面向 object 的编程中的常见设计模式。聚合意味着不多于也不少于数据分配。让我们看看您发布的 JSON 回复。正如我们所看到的,根 object 的字段集 属性 包含字段集 object 的 collection,我们可以通过 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);
}
此示例假定,名字和姓氏在部分实体的字段集 collection 的第一项中可用。所以我们得到 Bob Hogan
作为 return 值。 concatenate
方法只是一个小帮手,它将多个字符串与填充符 (space) 连接起来。
使用我们的实体和 FilterIterator 过滤数据class
您进一步提到,您必须通过 id 查找特定数据。一种可能的解决方案是使用 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;
}
}
因为我们的 collection 使用 ArrayObject
classes,所以我们能够使用迭代器来过滤特定参数。在这种情况下,我们在个人信息字段集中过滤 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 响应和 return 所需的数据。在 th 中使用 objects由于高可扩展性,s 案例使 mch 更有意义。您可以通过调用正确的 getter 方法一目了然地访问所有您想要的数据。此外,代码比一次又一次地递归迭代的巨大函数更具可读性。在上面显示的方法中,您只需编写一次代码并一次又一次地重复使用所有 object。
请记住,上面显示的代码只是一个理论上的建议。它没有经过测试。