在JavaScript中反序列化PHP数组

18

我有一个包含多行序列化数组的表格,我计划请求并传递给 JavaScript

问题在于 - 是否可以使用 JavaScript 而不是 PHP 进行 unserialize ?

否则,我将不得不加载所有行,循环它们并将它们反序列化并分配给临时 PHP 数组,然后再将其 json_encode 回 JavaScript,这似乎非常低效,如果我可以发送仍然序列化的数据,以便 JavaScript 在需要时对数据进行反序列化。

是否有内置的 JavaScript 函数可以执行此操作,还是我必须在编码之前在 PHP 中循环行?

请注意,我不使用 jQuery。

编辑: 来自我的表格的 PHP 中序列化数据的示例:

a:8:{i:0;a:2:{i:0;i:10;i:1;i:11;}i:1;a:2:{i:0;i:9;i:1;i:11;}i:2;a:2:
{i:0;i:8;i:1;i:11;}i:3;a:2:{i:0;i:8;i:1;i:10;}i:4;a:2:{i:0;i:8;i:1;i:9;}i:5;a:2:
{i:0;i:8;i:1;i:8;}i:6;a:2:{i:0;i:8;i:1;i:7;}i:7;a:2:{i:0;i:8;i:1;i:6;}}

2
你可以使用json_encode代替serialize吗?当在PHP和JS之间通信时,这可能更容易处理。http://php.net/manual/en/function.json-encode.php - dethtron5000
我在将数组存储到数据库之前对其进行序列化,以便存储和节省空间。我认为json_encode不能胜任此任务。 - Sir
那么我的唯一选择就是循环我选择的所有行,反序列化特定字段,然后使用该数据编码一个临时数组?这非常低效 :( - Sir
我一直以为序列化是最好的?还有其他的吗 :P - Sir
json_encode 是一种序列化数据的形式。只需使用 json_encode/json_decode 即可,无需费心处理序列化数据... - Justin E
显示剩余5条评论
4个回答

17

1
整个网站似乎已经死了: {"detail":"未找到"} - kungfooman
1
Github似乎仍然具有源代码https://github.com/hirak/phpjs - Nigel Ren

15

我想尝试编写一个JS函数,用于反序列化PHP序列化数据。

但在使用此解决方案之前,请注意:

  • PHP的serialize函数生成的格式是PHP特有的,因此最好使用PHP的unserialize以确保100%执行正确。
  • PHP可以在这些字符串中存储类信息,甚至可以存储一些自定义序列化方法的输出。因此,要对这些字符串进行反序列化,您需要了解这些类和方法。
  • PHP数据结构与JavaScript数据结构不对应:PHP关联数组可以具有字符串键,因此它们看起来更像JavaScript对象而不是JS数组,但在PHP中,键保持插入顺序,并且键可以具有真正的数字数据类型,这在JS对象中是不可能的。可以说我们应该查看JS中的Map对象,但是那些允许将13和“13”作为不同的键进行存储,而PHP不允许。我们仅仅触及到这个问题的表面......
  • PHP序列化对象的受保护和私有属性,这不仅很奇怪(多么“私有”啊?),而且是JS中不存在或者至少不是同样的概念。如果某种方式在JS中实现(硬)私有属性,那么一些反序列化如何能够设置这样的私有属性呢?
  • JSON是一个不特定于PHP的替代方案,也不关心自定义类。如果您可以访问进行序列化的PHP源代码,则将其更改为生成JSON。PHP提供json_encode,而JavaScript具有JSON.parse来解码它。如果可以,这肯定是正确的方法。

如果在这些备注后仍然需要JS反序列化函数,请继续阅读。

这里是一个JS实现,提供了一个PHP对象,与内置的JSON对象具有类似的方法:parsestringify

parse方法的输入引用类时,它将首先检查是否在(可选的)第二个参数中传递了对该类的引用。如果没有,则会为该类创建一个模拟对象(以避免不必要的副作用)。无论哪种情况,都将创建该类的实例。如果输入字符串指定发生了自定义序列化,则将调用该对象实例上的unserialize方法。您必须在该方法中提供逻辑,因为字符串本身不提供有关如何执行此操作的信息。这仅在生成该字符串的PHP代码中已知。

此实现还支持循环引用。如果关联数组最终是一个连续的数组,则将返回JS数组。

const PHP = {
    stdClass: function() {},
    stringify(val) {
        const hash = new Map([[Infinity, "d:INF;"], [-Infinity, "d:-INF;"], [NaN, "d:NAN;"], [null, "N;"], [undefined, "N;"]]); 
        const utf8length = str => str ? encodeURI(str).match(/(%.)?./g).length : 0;
        const serializeString = (s,delim='"') => `${utf8length(s)}:${delim[0]}${s}${delim[delim.length-1]}`;
        let ref = 0;
        
        function serialize(val, canReference = true) {
            if (hash.has(val)) return hash.get(val);
            ref += canReference;
            if (typeof val === "string") return `s:${serializeString(val)};`;
            if (typeof val === "number") return  `${Math.round(val) === val ? "i" : "d"}:${(""+val).toUpperCase().replace(/(-?\d)E/, "$1.0E")};`;
            if (typeof val === "boolean") return  `b:${+val};`;
            const a = Array.isArray(val) || val.constructor === Object;
            hash.set(val, `${"rR"[+a]}:${ref};`);
            if (typeof val.serialize === "function") {
                return `C:${serializeString(val.constructor.name)}:${serializeString(val.serialize(), "{}")}`;
            }
            const vals = Object.entries(val).filter(([k, v]) => typeof v !== "function");
            return (a ? "a" : `O:${serializeString(val.constructor.name)}`) 
                + `:${vals.length}:{${vals.map(([k, v]) => serialize(a && /^\d{1,16}$/.test(k) ? +k : k, false) + serialize(v)).join("")}}`;
        }
        return serialize(val);
    },
    // Provide in second argument the classes that may be instantiated
    //  e.g.  { MyClass1, MyClass2 }
    parse(str, allowedClasses = {}) {
        allowedClasses.stdClass = PHP.stdClass; // Always allowed.
        let offset = 0;
        const values = [null];
        const specialNums = { "INF": Infinity, "-INF": -Infinity, "NAN": NaN };

        const kick = (msg, i = offset) => { throw new Error(`Error at ${i}: ${msg}\n${str}\n${" ".repeat(i)}^`) }
        const read = (expected, ret) => expected === str.slice(offset, offset+=expected.length) ? ret 
                                         : kick(`Expected '${expected}'`, offset-expected.length);
        
        function readMatch(regex, msg, terminator=";") {
            read(":");
            const match = regex.exec(str.slice(offset));
            if (!match) kick(`Exected ${msg}, but got '${str.slice(offset).match(/^[:;{}]|[^:;{}]*/)[0]}'`);
            offset += match[0].length;
            return read(terminator, match[0]);
        }
        
        function readUtf8chars(numUtf8Bytes, terminator="") {
            const i = offset;
            while (numUtf8Bytes > 0) {
                const code = str.charCodeAt(offset++);
                numUtf8Bytes -= code < 0x80 ? 1 : code < 0x800 || code>>11 === 0x1B ? 2 : 3;
            }
            return numUtf8Bytes ? kick("Invalid string length", i-2) : read(terminator, str.slice(i, offset));
        }
        
        const create = className => !className ? {}
                    : allowedClasses[className] ? Object.create(allowedClasses[className].prototype)
                    : new {[className]: function() {} }[className]; // Create a mock class for this name
        const readBoolean = () => readMatch(/^[01]/, "a '0' or '1'", ";");
        const readInt     = () => +readMatch(/^-?\d+/, "an integer", ";");
        const readUInt    = terminator => +readMatch(/^\d+/, "an unsigned integer", terminator);
        const readString  = (terminator="") => readUtf8chars(readUInt(':"'), '"'+terminator);
        
        function readDecimal() {
            const num = readMatch(/^-?(\d+(\.\d+)?(E[+-]\d+)?|INF)|NAN/, "a decimal number", ";");
            return num in specialNums ? specialNums[num] : +num;
        }
        
        function readKey() {
            const typ = str[offset++];
            return typ === "s" ? readString(";") 
                 : typ === "i" ? readUInt(";")
                 : kick("Expected 's' or 'i' as type for a key, but got ${str[offset-1]}", offset-1);
        }
       
        function readObject(obj) {
            for (let i = 0, length = readUInt(":{"); i < length; i++) obj[readKey()] = readValue();
            return read("}", obj);
        }
        
        function readArray() {
            const obj = readObject({});
            return Object.keys(obj).some((key, i) => key != i) ? obj : Object.values(obj);
        }
        
        function readCustomObject(obj) {
            if (typeof obj.unserialize !== "function") kick(`Instance of ${obj.constructor.name} does not have an "unserialize" method`);
            obj.unserialize(readUtf8chars(readUInt(":{")));
            return read("}", obj);
        }
        
        function readValue() {
            const typ = str[offset++].toLowerCase();
            const ref = values.push(null)-1;
            const val = typ === "n" ? read(";", null)
                      : typ === "s" ? readString(";")
                      : typ === "b" ? readBoolean()
                      : typ === "i" ? readInt()
                      : typ === "d" ? readDecimal()
                      : typ === "a" ? readArray()                            // Associative array
                      : typ === "o" ? readObject(create(readString()))       // Object
                      : typ === "c" ? readCustomObject(create(readString())) // Custom serialized object
                      : typ === "r" ? values[readInt()]                      // Backreference
                      : kick(`Unexpected type ${typ}`, offset-1);
            if (typ !== "r") values[ref] = val;
            return val;
        }
        
        const val = readValue();
        if (offset !== str.length) kick("Unexpected trailing character");
        return val;
    }
}
/**************** EXAMPLE USES ************************/

// Unserialize a sequential array
console.log(PHP.parse('a:4:{i:0;s:4:"This";i:1;s:2:"is";i:2;s:2:"an";i:3;s:5:"array";}'));

// Unserialize an associative array into an object
console.log(PHP.parse('a:2:{s:8:"language";s:3:"PHP";s:7:"version";d:7.1;}'));

// Example with class that has custom serialize function:
var MyClass = (function () {
    const priv = new WeakMap(); // This is a way to implement private properties in ES6
    return class MyClass {
        constructor() {
            priv.set(this, "");
            this.wordCount = 0;
        }
        unserialize(serialised) {
            const words = PHP.parse(serialised);
            priv.set(this, words);
            this.wordCount = words.split(" ").length;
        }
        serialize() {
            return PHP.stringify(priv.get(this));
        }
    }
})();

// Unserialise a PHP string that needs the above class to work, and will call its unserialize method
// The class needs to be passed as object key/value as second argument, so to allow this side effect to happen:
console.log(PHP.parse('C:7:"MyClass":23:{s:15:"My private data";}', { MyClass } ));


5

使用 json_encodeunserialize 进行包装。

echo json_encode( unserialize( $array));

2
如果我从包含这种数据的表中选择多行会怎样? - Sir
然后,您需要将所有行推入另一个数组并发送完整的数组。 - charlietfl
该死,这正是我出于性能考虑所希望避免的。 - Sir
1
“performace reasons”? 它与循环行以生成其他输出(如HTML)或从字段创建JSON数组有多大不同? - charlietfl
4
如果使用JS,它将基于用户电脑的硬件进行操作,而不是我的服务器硬件,后者将需要不断地处理所有的访问者等。 - Sir
json_encode/decode()和un/serialize()在处理大量的大型数组或对象时(例如编码后的多个MB),不应成为瓶颈。 - JSON

1

11
当 PHP 序列化数据后,我们无法使用 JavaScript 的 JSON.parse 方法进行解析/反序列化。具有讽刺意味的是,所有 Stack Overflow 上对此类问题的回答都是使用 JSON.parse。 - dprobhat

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