class MigrateJSONReader in Migrate 7.2
An iterator over a JSON file. As is, this assumes that the file is structured as an array of objects, e.g.: [{"id":"53","field1":"value1"},{"id":"54","field1":"value2"}] To deal with different structures, extend this class and override next().
Hierarchy
- class \MigrateJSONReader implements \Iterator
Expanded class hierarchy of MigrateJSONReader
1 string reference to 'MigrateJSONReader'
- MigrateSourceJSON::__construct in plugins/
sources/ json.inc - Source constructor.
File
- plugins/
sources/ json.inc, line 184 - Support for migration from JSON sources.
View source
class MigrateJSONReader implements Iterator {
/**
* URL of the source JSON file.
*
* @var string
*/
public $url;
/**
* Handle of the JSON file we're currently parsing.
*
* @var resource
*/
protected $fileHandle;
/**
* Current element object when iterating.
*
* @var
*/
protected $currentElement = NULL;
/**
* Value of the ID for the current element when iterating.
*
* @var string
*/
protected $currentId = NULL;
/**
* Initialize the members.
*
* @param $json_url
* URL or filespec of the JSON file to be parsed.
* @param $id_field
* Name of the field within each object containing its unique ID.
*/
public function __construct($json_url, $id_field) {
$this->url = $json_url;
$this->idField = $id_field;
}
/**
* Implementation of Iterator::rewind().
*
* @return void
*/
public function rewind() {
// Close any open file - we open the files lazily in next().
if ($this->fileHandle) {
fclose($this->fileHandle);
$this->fileHandle = NULL;
}
// Load the first element.
$this
->next();
}
/**
* Obtain the next non-whitespace character from the JSON file.
*
* @return string
* The next non-whitespace character, or FALSE on end-of-file.
*/
protected function getNonBlank() {
while (($c = fgetc($this->fileHandle)) !== FALSE && trim($c) == '') {
}
return $c;
}
/**
* Implementation of Iterator::next().
*
* Populates currentElement (the object being retrieved) and currentId (that
* object's unique identifier) from the specified JSON file. Sets both to
* NULL at end-of-file. Handles properly-formed JSON, as well as some improper
* coding (specifically that generated in Ning exports).
*
* @return void
*/
public function next() {
migrate_instrument_start('MigrateJSONReader::next');
$this->currentElement = $this->currentId = NULL;
// Open the file and position it if necessary
if (!$this->fileHandle) {
$this->fileHandle = fopen($this->url, 'r');
if (!$this->fileHandle) {
Migration::displayMessage(t('Could not open JSON file !url', array(
'!url' => $this->url,
)));
return;
}
// We're expecting an array of characters, so the first character should be [.
$char = $this
->getNonBlank();
// Ning exports are wrapped in bogus (), so skip a leading (
if ($char == '(') {
$char = $this
->getNonBlank();
}
if ($char != '[') {
Migration::displayMessage(t('!url is not a JSON file containing an array of objects', array(
'!url' => $this->url,
)));
return;
}
}
// We expect to be positioned either at an object (beginning with {) or
// the end of the file (we should see a ] indicating this). Or, an
// object-separating comma, to be skipped. Note that this treats
// commas as optional between objects, which helps with processing
// malformed JSON with missing commas (as in Ning exports).
$c = $this
->getNonBlank();
if ($c == ',') {
$c = $this
->getNonBlank();
}
elseif ($c == ']') {
$c = $this
->getNonBlank();
if ($c != '{') {
$c = NULL;
}
}
// We expect to be at the first character of an object now.
if ($c == '{') {
// Start building a JSON string for this object.
$json = $c;
// Look for the closing }, ignoring brackets in strings, tracking nested
// brackets. Watch out for escaped quotes, but also note that \\" is not
// an escaped quote.
$depth = 1;
$in_string = FALSE;
$in_escape = FALSE;
while (($c = fgetc($this->fileHandle)) !== FALSE) {
$json .= $c;
if ($in_string) {
// Quietly accept an escaped character
if ($in_escape) {
$in_escape = FALSE;
}
else {
switch ($c) {
// Unescaped " means end of string
case '"':
$in_string = FALSE;
break;
// Unescaped \\ means start of escape
case '\\':
$in_escape = TRUE;
break;
}
}
}
else {
// Outside of strings, recognize {} as depth changes, " as start of
// string.
switch ($c) {
case '{':
$depth++;
break;
case '}':
$depth--;
break;
case '"':
$in_string = TRUE;
break;
}
// We've found our match, exit the loop.
if ($depth < 1) {
break;
}
}
}
// Turn the JSON string into an object.
$this->currentElement = json_decode($json);
$this->currentId = $this->currentElement->{$this->idField};
}
else {
$this->currentElement = NULL;
$this->currentId = NULL;
}
migrate_instrument_stop('MigrateJSONReader::next');
}
/**
* Implementation of Iterator::current().
*
* @return null|object
*/
public function current() {
return $this->currentElement;
}
/**
* Implementation of Iterator::key().
*
* @return null|string
*/
public function key() {
return $this->currentId;
}
/**
* Implementation of Iterator::valid().
*
* @return bool
*/
public function valid() {
return !empty($this->currentElement);
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
MigrateJSONReader:: |
protected | property | Current element object when iterating. | |
MigrateJSONReader:: |
protected | property | Value of the ID for the current element when iterating. | |
MigrateJSONReader:: |
protected | property | Handle of the JSON file we're currently parsing. | |
MigrateJSONReader:: |
public | property | URL of the source JSON file. | |
MigrateJSONReader:: |
public | function | Implementation of Iterator::current(). | |
MigrateJSONReader:: |
protected | function | Obtain the next non-whitespace character from the JSON file. | |
MigrateJSONReader:: |
public | function | Implementation of Iterator::key(). | |
MigrateJSONReader:: |
public | function | Implementation of Iterator::next(). | |
MigrateJSONReader:: |
public | function | Implementation of Iterator::rewind(). | |
MigrateJSONReader:: |
public | function | Implementation of Iterator::valid(). | |
MigrateJSONReader:: |
public | function | Initialize the members. |