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 ?

share|improve this question
6  
Please explain why you can't JSONify it. That would simplify everything a whole lot. – gnud Oct 12 '09 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. – Alsciende Oct 12 '09 at 11:55
Yes, I got that. I was asking why can't you make it into valid json in js? – gnud Oct 12 '09 at 11:55
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. – Alsciende Oct 12 '09 at 11:59
I think you should tell that someone that his API is broken – soulmerge Oct 12 '09 at 12:14
show 3 more comments

3 Answers

up vote 3 down vote accepted

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.

share|improve this answer
Thanks, it's perfect :) – Alsciende Oct 12 '09 at 12:31

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}',
share|improve this answer
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 Oct 12 '09 at 12:55
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 Oct 12 '09 at 13:00
I think it will now, I've fixed some bugs for dicts in dicts and arrays in arrays. Your test string works now. – gnud Oct 12 '09 at 13:04
This version handles quoted keys, and converts undefined to null. – gnud Oct 12 '09 at 13:47

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

share|improve this answer
I need a php library, not a JS library. – Alsciende Oct 12 '09 at 12:12
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 Oct 12 '09 at 14:32

Your Answer

 
or
required, but never shown
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.