在PHP中解析JavaScript(非JSON)

14

我有一个包含javascript对象序列化的PHP字符串:

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

当然,实际字符串要复杂得多,但仍是格式良好的JavaScript。这不是标准JSON,因此json_decode会失败。您知道任何能解析此字符串并返回php关联数组的php库吗?


7
请解释一下为什么不能将其转换成 JSON 格式,这样会使一切变得简单许多。 - gnud
是的,我明白了。我在问为什么你不能在js中将它转换为有效的json格式? - gnud
1
我正在回复一条已被删除的评论。 我无法创建有效的字符串,因为不是我在创建字符串,而是其他人。 - Alsciende
2
这不是标准的JSON格式,但它是标准的Javascript语法。因此我的问题标题是这样的。 - Alsciende
5
8条评论才能得出一个早已显而易见的结论。干得好:D - jpeltoniemi
显示剩余4条评论
7个回答

20
这听起来像是一个有趣的挑战,所以我编写了一个小解析器: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.')');
    }
}

使用方法:

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

提供:

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

使用以下字符串进行测试:

'{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}',

1
@gnud 你的代码在以下 JavaScript 对象上出现错误 {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}]}。 - Dr. DS
此外,如果 JavaScript 对象在顶层是一个数组,它可能以 [ 开头。例如:[5,4,3,'too',"Juan"] - Jesse Chisholm
这很不错,但是它不太能很好地处理多行对象。当它在一行上遇到一个单独的 }, 时,它似乎认为它已经完成了(过早地)。而且由于某种原因,递归太深会导致它抛出无效键错误,并且引号等方面对齐不正确?但还是感谢提供起点。 - Tustin2121
整洁 - 我喜欢它 :D - Frank
当我设置{key:[{id:1,price:1000,commnet:"foo",memo:[]},{id:2,price:2000,memo:[]}]}时,出现了错误。因此,我开发了一个库来解决这个问题。所有测试字符串都已通过。我希望它对每个人都有用。https://github.com/Osushi/jsobj2php - Osushi
显示剩余4条评论

11

Pear Services_JSON将解析该字符串(测试版本为1.31)。但是,由于这不是有效的JSON,因此无法保证未来的版本仍将起作用。


2

感谢luttkens。

Yii框架的CJSON::decode()类完美运行!

最初的回答已经能够满足需求。

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
        )

)

2
我发现Yii框架的CJSON::decode()函数也可以处理Javascript对象。
如果您没有使用Yii,您应该能够直接使用源代码

对于编码 JavaScript 对象(和标量值),请查看 CJavaScript,它在编码方面做得非常好。 - PeterM

0

0
这是一个最简单的方法:
<?php

function jsObjectToJSON($objstring)
{
    $objstring = preg_replace('/(\w+):/i', '"$1":', $objstring); // replace key with "key"
    $objstring = preg_replace('/\'/i', '"', $objstring); // replace ' with "
    return $objstring;
}

// usage
$objstring = '{foo: "bar", baz: "qux"}';
$json = jsObjectToJSON($objstring);
echo $json; // {"foo": "bar", "baz": "qux"}

// now you can use json_decode() on the result
var_dump(json_decode($json, true));
// array(2) {
//   ["foo"]=>
//   string(3) "bar"
//   ["baz"]=>
//   string(3) "qux"
// }


0
PHP不适用于解析(复杂的)包含函数、变量、语法等的Javascript字符串。
你应该使用浏览器来完成这个任务。我们有chrome-php与Chrome无头模式。
安装它,并使用类似以下的方法。
$evaluation = $page->callFunction(
   "function(a, b) {\n    window.foo = '{fu:\"bar\",baz:[\"bat\"]}';\n}",
   [1, 2]
);

$value = $evaluation->getReturnValue();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接