Commit ad60821f authored by mikeryan's avatar mikeryan Committed by mikeryan
Browse files

Issue #2640514 by mikeryan: Implement xpath-like selectors for the JSON parser

parent 2a1f208f
......@@ -11,13 +11,14 @@ source:
# we can't hardcode your local URL, we provide a relative path here which
# hook_install() will rewrite to a full URL for the current site.
urls: /migrate_example_advanced_position?_format=json
item_selector: 1
# An xpath-like selector corresponding to the items to be imported.
item_selector: position
# Under 'fields', we list the data items to be imported. The first level keys
# are the source field names we want to populate (the names to be used as
# sources in the process configuration below). For each field we're importing,
# we provide a label (optional - this is for display in migration tools) and
# an xpath for retrieving that value. It's important to note that this xpath
# is relative to the elements retrieved by item_xpath.
# is relative to the elements retrieved by item_selector.
fields:
-
name: machine_name
......
......@@ -32,22 +32,53 @@ class Json extends DataParserPluginBase implements ContainerFactoryPluginInterfa
protected $iterator;
/**
* {@inheritdoc}
* Retrieves the JSON data and returns it as an array.
*
* @param string $url
* URL of a JSON feed.
*
* @return array
* The selected data to be iterated.
*
* @throws \GuzzleHttp\Exception\RequestException
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
protected function getSourceData($url) {
$response = $this->getDataFetcherPlugin()->getResponseContent($url);
// Convert objects to associative arrays.
$source_data = json_decode($response, TRUE);
// Backwards-compatibility for depth selection.
if (is_int($this->itemSelector)) {
return $this->selectByDepth($source_data);
}
// Otherwise, we're using xpath-like selectors.
$selectors = explode('/', trim($this->itemSelector, '/'));
foreach ($selectors as $selector) {
$source_data = $source_data[$selector];
}
return $source_data;
}
/**
* {@inheritdoc}
* Get the source data for reading.
*
* @param array $raw_data
* Raw data from the JSON feed.
*
* @return array
* Selected items at the requested depth of the JSON feed.
*/
protected function getSourceData($url) {
$iterator = $this->getSourceIterator($url);
// Recurse through the result array. When there is an array of items at the
// expected depth, pull that array out as a distinct item.
$identifierDepth = $this->itemSelector;
protected function selectByDepth(array $raw_data) {
// Return the results in a recursive iterator that can traverse
// multidimensional arrays.
$iterator = new \RecursiveIteratorIterator(
new \RecursiveArrayIterator($raw_data),
\RecursiveIteratorIterator::SELF_FIRST);
$items = [];
// Backwards-compatibility - an integer item_selector is interpreted as a
// depth. When there is an array of items at the expected depth, pull that
// array out as a distinct item.
$identifierDepth = $this->itemSelector;
$iterator->rewind();
while ($iterator->valid()) {
$item = $iterator->current();
......@@ -59,33 +90,6 @@ class Json extends DataParserPluginBase implements ContainerFactoryPluginInterfa
return $items;
}
/**
* Get the source data for reading.
*
* @param string $url
* The URL to read the source data from.
*
* @return \RecursiveIteratorIterator|resource
*
* @throws \Drupal\migrate\MigrateException
*/
protected function getSourceIterator($url) {
try {
$response = $this->getDataFetcherPlugin()->getResponseContent($url);
// The TRUE setting means decode the response into an associative array.
$array = json_decode($response, TRUE);
// Return the results in a recursive iterator that
// can traverse multidimensional arrays.
return new \RecursiveIteratorIterator(
new \RecursiveArrayIterator($array),
\RecursiveIteratorIterator::SELF_FIRST);
}
catch (RequestException $e) {
throw new MigrateException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
......@@ -103,7 +107,12 @@ class Json extends DataParserPluginBase implements ContainerFactoryPluginInterfa
$current = $this->iterator->current();
if ($current) {
foreach ($this->fieldSelectors() as $field_name => $selector) {
$this->currentItem[$field_name] = $current[$selector];
$field_data = $current;
$field_selectors = explode('/', trim($selector, '/'));
foreach ($field_selectors as $field_selector) {
$field_data = $field_data[$field_selector];
}
$this->currentItem[$field_name] = $field_data;
}
$this->iterator->next();
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment