Обход глубоко вложенного объекта JSON - PullRequest
0 голосов
/ 22 ноября 2018

При взаимодействии с API, используемым для создания форм, я выполняю вызов API, чтобы получить все значения ответов, связанные с моей формой.API возвращает глубоко вложенный объект 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"
        }
      ]
    }

Целью здесь является запуск отчетавсех ответов и распечатайте данные в удобочитаемом виде (например, «Боб Хоган - 200 долларов, Чад Смит - 100 долларов»).

Я думаю, мне придется использовать какую-то карту-снизить алгоритм, потому что простое вложение группы циклов может быть не масштабируемым, а также вычислительно дорогостоящим, учитывая растущую сложность времени, если это большой набор данных.Может быть, мне нужно написать рекурсивную функцию, которая сопоставляет мой набор данных, проверяет значение идентификатора, сокращает его до массива, если находит соответствующий идентификатор?

Кроме того, я бы хотел избежать использования сторонних производителей.библиотека.PHP имеет достаточно встроенных функций, чтобы облегчить то, что я пытаюсь выполнить.

1 Ответ

0 голосов
/ 25 ноября 2018

На самом деле нет необходимости в магическом алгоритме.Просто немного магии php в виде энтитов, гидраторов и фильтров.

В этом ответе вы получите объектно-ориентированный подход php, который будет гидрировать ответ json api на объекты, которые вы можете легко отфильтровать.Просто имейте в виду, что в этом oop apporach все является объектом.

Объект данных - сущность данных

Прежде всего вы должны знать, как ваши данныеструктурирован.Из этой структуры вы можете создавать объекты 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, приведенного в вашем примере.Почему этот класс реализует интерфейс JsonSerializable ?С помощью этой реализации вы можете преобразовать структуру класса обратно в правильно сформированную строку json.Я не уверен, если вам это нужно.Но наверняка это безопасно, пока общаешься с остальными api.Единственное, что вам нужно сделать сейчас - это программировать сущности для каждого ожидаемого сложного объекта данных / объекта json.Вам нужен титровальный объект со свойством plin и объект fieldset со свойствами label и 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 сложны и вложены, мы должны использовать гидраторные стратегии.Распространенная закономерность в проблемах гидратации.Стратегия может быть подключена к свойству объекта и выполняет другой гидратор.Поэтому мы уверены, что представляем вложенные данные в идентичной объектной структуре.

Вот пример стратегии гидратора.

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 в ассоциативный многомерный массив.После этого мы используем наши сущности, гидраторы и стратегии гидраторов, чтобы получить объект, который содержит все данные.Вариант использования знает, что свойство title в ответе JSON является объектом, который должен быть преобразован в нашу сущность title, которая содержит свойство plain.

В конце наш гидратированный объект имеет такую ​​структуру ...

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

На самом деле вы можете получить доступ к свойствам с помощью методов получения наших сущностей.

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

Знание того, как увлажнять наши занятия, приводит нас к следующему шагу.Агрегация!

Получение полной строки с агрегацией

На самом деле агрегация - это распространенный шаблон проектирования в современном объектно-ориентированном программировании.Агрегация означает не больше и не меньше, чем распределение данных.Давайте посмотрим на ваш опубликованный ответ JSON.Как мы видим, свойство fieldset нашего корневого объекта содержит коллекцию объектов fieldset, к которым мы можем получить доступ через наши методы getter и setter.Имея это в виду, мы можем создать дополнительные методы получения в нашей сущности раздела.Давайте расширим нашу сущность раздела методом 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);
}

В этом примере предполагается, что как имя, так и фамилия доступны в самом первом элементе коллекции fieldset объекта section.Таким образом, мы получаем Bob Hogan в качестве возвращаемого значения.Метод concatenate - это всего лишь маленький помощник, который объединяет несколько строк с заполнителем (пробелом).

Фильтрация данных с использованием наших сущностей и класса FilterIterator

Далее вы упомянули, что вам нужно найти конкретные данные по id.Одним из возможных решений может быть фильтрация наших сущностей по определенному элементу с помощью класса Filter Iterator .

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 для наших коллекций мы можем использовать итераторы дляфильтр для конкретного аргумента.В этом случае мы фильтруем идентификатор в наших наборах персональных данных.

Начиная с нашего примера гидратации, мы можем использовать что-то вроде следующего кода.

$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 и возвращает нужные данные.Работа с объектами в этом случае имеет больше смысла из-за высокой масштабируемости.Вы можете получить доступ ко всем данным, которые вы хотите, мельком, вызвав правильные методы получения.Кроме того, код более читабелен, чем огромная функция, которая рекурсивно повторяется снова и снова.В приведенном выше подходе вы кодируете только один раз и снова и снова используете все объекты.

Пожалуйста, имейте в виду, что приведенный выше код является лишь теоретическим предложением.Это не проверено.

...