tags:

views:

211

answers:

3

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 ?

A: 

Use JS code by Morten Amundsen, here or here.

It gives you a JS lib to serialize your JS data the same way PHP can unserialize

AlberT
I need a php library, not a JS library.
Alsciende
I suggested you one possible solution, client side. Never told it is _the_ solution for you, but it is _a_ solution. Hope useful to other ppl. Tnx for donwvote
AlberT
+3  A: 

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.

Tom Haigh
Thanks, it's perfect :)
Alsciende
+2  A: 

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}',
gnud
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.
Alsciende
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"}}'
Alsciende
I think it will now, I've fixed some bugs for dicts in dicts and arrays in arrays. Your test string works now.
gnud
This version handles quoted keys, and converts undefined to null.
gnud