3

I need to convert an XML document into JSON, in order to easily access the data in JavaScript. I am currently using this method to convert the XML into JSON:

json_encode(new SimpleXMLElement($xml, LIBXML_NOCDATA));

However, I ran into a problem when an element only contains 1 child element. When it is parsed with SimpleXML, it is treated as an object instead of an array. I want them to always be treated as arrays, unless the element only contains text.

Example:

$xml = <<<END
<xml>
  <TESTS>
    <TEST>TEXT HERE</TEST>
  </TESTS>
</xml>
END;

echo json_encode(new SimpleXMLElement($xml, LIBXML_NOCDATA));

This outputs:

{"TESTS":{"TEST":"TEXT HERE"}}

If I add another element under , then the output is how I want it:

$xml = <<<END
<xml>
  <TESTS>
    <TEST>TEXT HERE</TEST>
    <TEST>MORE TEXT HERE</TEST>
  </TESTS>
</xml>
END;

echo json_encode(new SimpleXMLElement($xml, LIBXML_NOCDATA));

Output:

{"TESTS":{"TEST":["TEXT HERE","TEXT HERE"]}}

Note how the elements are contained inside a JSON array instead of a JSON object. Is there a way to force elements to be parsed as arrays?

6
  • It seems that you have object anyway but the value of this element in the first case is string but in the second is array. Commented Aug 24, 2011 at 19:44
  • I think you'll have to walk the XML and manually convert to arrays. Even if there is a way to force arrays instead of objects, that first example will end up with this for JSON: {"Tests":[{"Test":["Text HERE"]}]}. I'm thinking that's not what you really want. Commented Aug 24, 2011 at 19:47
  • you're passing in an object (of class simplexml)... of course you're going to get an object out of json_encode. Commented Aug 24, 2011 at 19:58
  • @Marc B I think the OP is looking for the array analagous of the JSON_FORCE_OBJECT option Commented Aug 24, 2011 at 20:15
  • It seems that your second example - SimpleXMLElement falling back on an array for representing the <TEST> nodes - happens just because there is no alternative. You can't have two properties named TEST in the same object. So SimpleXML uses an array as an act of desperation. With one item, that just won't happen. So you'll probably have to pick apart the XML yourself, as the other commenters have already said. Commented Aug 31, 2011 at 20:27

2 Answers 2

2

I had to deal with the same situation. Here is a quick first solution to the problem - works fine for your example.

class JsonWithArrays
{
    protected $root, $callback;

    public function __construct( SimpleXMLElement $root )
    {
        $this->root = $root;
    }

    /**
     * Set a callback to return if a node should be represented as an array
     * under any circumstances.
     *
     * The callback receives two parameters to react to: the SimpleXMLNode in
     * question, and the nesting level of that node.
     */
    public function use_callback_forcing_array ( $callback )
    {
        $this->callback = $callback;
        return $this;
    }

    public function to_json ()
    {
        $transformed = $this->transform_subnodes( $this->root, new stdClass(), 0 );
        return json_encode( $transformed );
    }

    protected function transform_subnodes ( SimpleXMLElement $parent, stdClass $transformed_parent, $nesting_level )
    {
        $nesting_level++;

        foreach( $parent->children() as $node )
        {
            $name = $node->getName();
            $value = (string) $node;

            if ( count( $node->children() ) > 0 )
            {
                $transformed_parent->$name = new stdClass();
                $this->transform_subnodes( $node, $transformed_parent->$name, $nesting_level );
            }
            elseif ( count( $parent->$name ) > 1 or $this->force_array( $node, $nesting_level ) )
            {
                $transformed_parent->{$name}[] = $value;
            }
            else
            {
                $transformed_parent->$name = $value;
            }
        }

        return $transformed_parent;
    }

    protected function force_array ( $node, $nesting_level )
    {
        if ( is_callable( $this->callback ) )
        {
            return call_user_func( $this->callback, $node, $nesting_level );
        }
        else
        {
            return false;
        }
    }
}

$xml = <<<END
<xml> 
  <TESTS> 
    <TEST>TEXT HERE</TEST> 
  </TESTS> 
</xml> 
END;

$xml2 = <<<END
<xml> 
  <TESTS> 
    <TEST>TEXT HERE</TEST> 
    <TEST>MORE TEXT HERE</TEST> 
  </TESTS> 
</xml> 
END;

// Callback using the node name. Could just as well be done using the nesting
// level.
function cb_testnode_as_array( SimpleXMLElement $node, $nesting_level )
{
    return $node->getName() == 'TEST';
}

$transform = new JsonWithArrays( new SimpleXMLElement($xml, LIBXML_NOCDATA) );
echo $transform
    ->use_callback_forcing_array( 'cb_testnode_as_array' )
    ->to_json();

echo PHP_EOL;

$transform2 = new JsonWithArrays( new SimpleXMLElement($xml2, LIBXML_NOCDATA) );
echo $transform2
    ->use_callback_forcing_array( 'cb_testnode_as_array' )
    ->to_json();
Sign up to request clarification or add additional context in comments.

Comments

0
echo json_encode(json_decode(json_encode(new SimpleXMLElement($xml, LIBXML_NOCDATA)), true));

Really this is lot a stupid but you first if all convert to object then decode in array and convert to json like array style.. ))

1 Comment

Tried it, didn't work. Returns {"TESTS":{"TEST":"TEXT HERE"}} just as the code posted by the OP.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.