18

I have a table with a load of rows of serialized arrays that I plan to request and pass it to JavaScript.

The problem is - is it possible to unserialize with JavaScript rather than PHP ?

Otherwise I will have to load all the rows, loop them and unserialize them and assign them to a temporary PHP array and then json_encode it back to JavaScript which seems highly inefficient if I can send the data still serialized so that JavaScript can unserialize the data when it needs to.

Is there a built in Javascript function that does it or will I have to loop the rows in PHP before I encode it?

Note I am not using jQuery.

EDIT: Example of my serialized data in PHP from my table:

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;}}
10
  • 2
    Can you use json_encode instead of serialize? That may be easier to work with when communicating between PHP and JS. php.net/manual/en/function.json-encode.php Commented Jan 9, 2013 at 2:29
  • I serialize the arrays before i store them into my database so i can store it and save space. I don't think json_encode would do the job. Commented Jan 9, 2013 at 2:31
  • @Dave But you can unserialize in PHP and then JSON encode. Commented Jan 9, 2013 at 2:33
  • Maybe unserialize after retrieval and then send as json? Also, serialization adds a fair amount of extra data so you may not be saving much space in the long run. Commented Jan 9, 2013 at 2:33
  • So my only choice is to loop ALL the rows i select to unserialize the specific fields then encode a temp array with that data? Thats very inefficient :( Commented Jan 9, 2013 at 2:34

4 Answers 4

18

Php.js has javascript implementations of unserialize and serialize:

http://phpjs.org/functions/unserialize/

http://phpjs.org/functions/serialize/

That said, it's probably more efficient to convert to JSON on the server side. JSON.parse is going to be a lot faster than PHP.js's unserialize.

Sign up to request clarification or add additional context in comments.

2 Comments

Entire website seems dead: {"detail":"Not Found"}
Github still seems to have the source github.com/hirak/phpjs
16

I thought I would have a go at writing a JS function that can unserialize PHP-serialized data.

But before going for this solution please be aware that:

  • The format produced by PHP's serialize function is PHP specific and so the best option is to use PHP's unserialize to have 100% guarantee that it is doing the job right.
  • PHP can store class information in those strings and even the output of some custom serialization methods. So to unserialize such strings you would need to know about those classes and methods -- this is information that is not included in the serialized string.
  • PHP data structures do not correspond 1-to-1 to JavaScript data structures: PHP associative arrays can have strings as keys, and so they look more like JavaScript objects than JS arrays, but in PHP the keys keep insertion order, and keys can have a truly numeric data type which is not possible with JS objects. One could say that then we should look at Map objects in JS, but those allow to store 13 and "13" as separate keys, which PHP does not allow. And we're only touching the tip of the iceberg here...
  • PHP serializes protected and private properties of objects, which not only is strange (how private is that?), but protected is a concept that does not (yet) exist in JS, or at least not in the same way. And when unserializing private properties in JS, the target object would already need to have those private fields. When you want to support private property names "just as they come" -- without foreknowledge -- then you'd need to fall back on a WeakMap for creating them on the spot.
  • JSON is an alternative that is not specific to PHP, and does not care about custom classes either. If you can access the PHP source of were the serialization happens, then change this to produce JSON instead. PHP offers json_encode for that, and JavaScript has JSON.parse to decode it. This is certainly the way to go if you can.

If with those remarks you still see the need for a JS unserialise function, then read on.

Here is a JS implementation that provides a PHP object with similar methods as the built-in JSON object: parse and stringify.

When an input to the parse method references a class, then it will first check if you passed a reference to that class in the (optional) second argument. If not, a mock will be created for that class (to avoid undesired side-effects). In either case an instance of that class will be created. If the input string specifies a custom serialization happened then the method unserialize on that object instance will be called. You must provide the logic in that method as the string itself does not give information about how that should be done. It is only known in the PHP code that generated that string.

This implementation also supports cyclic references. When an associative array turns out to be a sequential array, then a JS array will be returned.

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 () {
    // This is a way to implement private properties that have 
    //     names that are not pre-determined:
    const priv = new WeakMap(); 
    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 } ));

Comments

5

wrap json_encode around unserialize

echo json_encode( unserialize( $array));

6 Comments

What if i'm selecting multiple rows from a table with such data?
then you need to push all the rows into another array and send the full array
Damn thats what i was hoping to avoid for performance reasons
performace reasons? How much different is it than looping over rows to generate other output such as html, or creating arrays of json from fields?
well if JS was doing it - it would be based on the hardware of the user's pc not my server hardware which would be doing it constantly from all the visitors etc.
|
1

http://php.net/manual/en/book.json.php

Just noticed your comment so here we go:

in PHP

json_encode(unserialize(SerializedVal));

in JavaScript:

JSON.parse(JsonString);

1 Comment

when PHP serializes we cannot parse/unserialize it with javascript json parse. And irony is all SO questions has same answers JSON.parse.

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.