16

I have a php string containing the serialization of a javascript object :

$string = '{fu:"bar",baz:["bat"]}';

The actual string is far more complicated, of course, but still well-formed javascript. This is not standard JSON, so json_decode fails. Do you know any php library that would parse this string and return a php associative array ?

9
  • 7
    Please explain why you can't JSONify it. That would simplify everything a whole lot. Commented Oct 12, 2009 at 11:47
  • As said, this is not valid JSON. Valid JSON requires objects keys to be formated like strings, ie enclosed with double quotes. Commented Oct 12, 2009 at 11:55
  • 1
    I was replying to a now-deleted comment. I can't make a valid string because I'm not making the string. Someone else does. Commented Oct 12, 2009 at 11:59
  • 2
    It's not standard JSON, but it's standard Javascript. Hence the title of my question. Commented Oct 12, 2009 at 12:22
  • 6
    8 comments to get to something that was already obvious. Well done :D Commented Mar 5, 2014 at 12:09

6 Answers 6

20

This sounded like a fun challenge, so I coded up a tiny parser :D

class JsParserException extends Exception {}
function parse_jsobj($str, &$data) {
    $str = trim($str);
    if(strlen($str) < 1) return;

    if($str{0} != '{') {
        throw new JsParserException('The given string is not a JS object');
    }
    $str = substr($str, 1);

    /* While we have data, and it's not the end of this dict (the comma is needed for nested dicts) */
    while(strlen($str) && $str{0} != '}' && $str{0} != ',') { 
        /* find the key */
        if($str{0} == "'" || $str{0} == '"') {
            /* quoted key */
            list($str, $key) = parse_jsdata($str, ':');
        } else {
            $match = null;
            /* unquoted key */
            if(!preg_match('/^\s*[a-zA-z_][a-zA-Z_\d]*\s*:/', $str, $match)) {
            throw new JsParserException('Invalid key ("'.$str.'")');
            }   
            $key = $match[0];
            $str = substr($str, strlen($key));
            $key = trim(substr($key, 0, -1)); /* discard the ':' */
        }

        list($str, $data[$key]) = parse_jsdata($str, '}');
    }
    "Finshed dict. Str: '$str'\n";
    return substr($str, 1);
}

function comma_or_term_pos($str, $term) {
    $cpos = strpos($str, ',');
    $tpos = strpos($str, $term);
    if($cpos === false && $tpos === false) {
        throw new JsParserException('unterminated dict or array');
    } else if($cpos === false) {
        return $tpos;
    } else if($tpos === false) {
        return $cpos;
    }
    return min($tpos, $cpos);
}

function parse_jsdata($str, $term="}") {
    $str = trim($str);


    if(is_numeric($str{0}."0")) {
        /* a number (int or float) */
        $newpos = comma_or_term_pos($str, $term);
        $num = trim(substr($str, 0, $newpos));
        $str = substr($str, $newpos+1); /* discard num and comma */
        if(!is_numeric($num)) {
            throw new JsParserException('OOPSIE while parsing number: "'.$num.'"');
        }
        return array(trim($str), $num+0);
    } else if($str{0} == '"' || $str{0} == "'") {
        /* string */
        $q = $str{0};
        $offset = 1;
        do {
            $pos = strpos($str, $q, $offset);
            $offset = $pos;
        } while($str{$pos-1} == '\\'); /* find un-escaped quote */
        $data = substr($str, 1, $pos-1);
        $str = substr($str, $pos);
        $pos = comma_or_term_pos($str, $term);
        $str = substr($str, $pos+1);        
        return array(trim($str), $data);
    } else if($str{0} == '{') {
        /* dict */
        $data = array();
        $str = parse_jsobj($str, $data);
        return array($str, $data);
    } else if($str{0} == '[') {
        /* array */
        $arr = array();
        $str = substr($str, 1);
        while(strlen($str) && $str{0} != $term && $str{0} != ',') {
            $val = null;
            list($str, $val) = parse_jsdata($str, ']');
            $arr[] = $val;
            $str = trim($str);
        }
        $str = trim(substr($str, 1));
        return array($str, $arr);
    } else if(stripos($str, 'true') === 0) {
        /* true */
        $pos = comma_or_term_pos($str, $term);
        $str = substr($str, $pos+1); /* discard terminator */
        return array(trim($str), true);
    } else if(stripos($str, 'false') === 0) {
        /* false */
        $pos = comma_or_term_pos($str, $term);
        $str = substr($str, $pos+1); /* discard terminator */
        return array(trim($str), false);
    } else if(stripos($str, 'null') === 0) {
        /* null */
        $pos = comma_or_term_pos($str, $term);
        $str = substr($str, $pos+1); /* discard terminator */
        return array(trim($str), null);
    } else if(strpos($str, 'undefined') === 0) {
        /* null */
        $pos = comma_or_term_pos($str, $term);
        $str = substr($str, $pos+1); /* discard terminator */
        return array(trim($str), null);
    } else {
        throw new JsParserException('Cannot figure out how to parse "'.$str.'" (term is '.$term.')');
    }
}

Usage:

$data = '{fu:"bar",baz:["bat"]}';    
$parsed = array();    
parse_jsobj($data, $parsed);    
var_export($parsed);

Gives:

array (
  'fu' => 'bar',
  'baz' =>
  array (
    0 => 'bat',
  ),
)

Tested with these strings:

'{fu:"bar",baz:["bat"]}',
'{rec:{rec:{rec:false}}}',
'{foo:[1,2,[3,4]]}',
'{fu:{fu:"bar"},bar:{fu:"bar"}}',
'{"quoted key":[1,2,3]}',
'{und:undefined,"baz":[1,2,"3"]}',
'{arr:["a","b"],"baz":"foo","gar":{"faz":false,t:"2"},f:false}',
Sign up to request clarification or add additional context in comments.

10 Comments

It's really really nice. Two remarks : Javascript object keys can be enclosed in simple quotes or doubles quotes (usually if the key is not a valid identifier (eg contains a space)). And undefined is a valid value.
I guess it won't work if an object is included in the value of another object and strrpos isn't right, eg '{fu:{fu:"bar"},bar:{fu:"bar"}}'
I think it will now, I've fixed some bugs for dicts in dicts and arrays in arrays. Your test string works now.
This version handles quoted keys, and converts undefined to null.
@gnud your code breaks on following javascript object {name:"Andrew", age: "11", toys: { car: [{color:"red", wheel: "1"} ,{color:"white", wheel: "4"}]}, bus: [ {av: "Mug 2013", var: [ {color:"red", wheel: "10"} ,{color:"white", wheel: "34"}], totl: 10,buy: true}]}
|
11

Pear Services_JSON will parse that string (tested version 1.31). But given that that is a JSON parser and that this isn't valid JSON you have no guarantee that future versions will still work.

Comments

3

I found out that the Yii-framework's CJSON::decode() function handles Javascript objects as well.

If you're not using Yii, you should be able to just use the source code

1 Comment

For encoding JavaScript objects (and scalar values) check out CJavaScript which does great job with encoding.
2

thank luttkens

the CJON::decode() class of the Yii-framework works perfectly !

require_once ($_SERVER['DOCUMENT_ROOT']."/phplib/CJSON.php");

$json = new CJSON();
$data = $json->decode('{ url : "/jslib/maps/marker/marker_red.png", height : 34, width : 20, anchorIcon : [5,25.5], anchorText : [0,2], }', true);

print_r( $data );

result :

Array
(
    [url] => /jslib/maps/marker/marker_red.png
    [height] => 34
    [width] => 20
    [anchorIcon] => Array
        (
            [0] => 5
            [1] => 25.5
        )

    [anchorText] => Array
        (
            [0] => 0
            [1] => 2
        )

)

Comments

0

What about that library? http://timwhitlock.info/tag/jparser/

I haven't tried it yet.

Comments

0

PHP does not design for Parsing (Complicate) Javascript String, which contains function, variable, syntax, etc ...
You should use Browser for this task. And we have chrome-php with chrome headless mode.
Install it, and use something like this.

$evaluation = $page->callFunction(
   "function(a, b) {\n    window.foo = '{fu:\"bar\",baz:[\"bat\"]}';\n}",
   [1, 2]
);

$value = $evaluation->getReturnValue();

Comments

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.