web-dev-qa-db-fra.com

Désérialiser PHP Tableau en Javascript

J'ai une table avec un chargement de rangées de tableaux sérialisés que je prévois de demander et de le transmettre à JavaScript.

Le problème est - est-il possible de unserialize avec JavaScript plutôt que PHP?

Sinon, je devrai charger toutes les lignes, les boucler, les désérialiser et les attribuer à un tableau temporaire PHP, puis le retourner au code JavaScript qui semble très inefficace si je peux envoyer les données encore sérialisées JavaScript peut désérialiser les données en cas de besoin.

Existe-t-il une fonction Javascript intégrée qui le fait ou devrai-je boucler les lignes dans PHP avant de l'encoder?

Notez que je n'utilise pas jQuery.

EDIT: Exemple de mes données sérialisées dans PHP de ma 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;}}
8
Sir

envelopper json_encode autour de unserialize

echo json_encode( unserialize( $array));
5
charlietfl

Php.js a des implémentations javascript desserialize et serialize:

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

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

Cela dit, il est probablement plus efficace de convertir en JSON côté serveur. JSON.parse va être beaucoup plus rapide que la désérialisation de PHP.js.

12
snostorm

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

Je viens juste de remarquer ton commentaire, alors on y va:

en PHP

json_encode(unserialize(SerializedVal));

en JavaScript:

JSON.parse(JsonString);
3
HMR

Je pensais pouvoir écrire une fonction JS capable de désérialiser les données sérialisées PHP.

Mais avant de choisir cette solution, veuillez noter que:

  • Le format produit par la fonction serialize de PHP est spécifique à PHP. La meilleure option consiste donc à utiliser la variable unserialize de PHP afin de garantir à 100% qu'elle fonctionne correctement.
  • PHP peut stocker des informations de classe dans ces chaînes et même la sortie de certaines méthodes de sérialisation personnalisées. Donc, pour désérialiser de telles chaînes, vous devez connaître ces classes et méthodes.
  • Les structures de données PHP ne correspondent pas 1 à 1 aux structures de données JavaScript: PHP les tableaux associatifs peuvent avoir des chaînes comme clés. Ils ressemblent donc davantage à des objets JavaScript qu'à des tableaux JS, mais en PHP les touches conservent l'ordre d'insertion et peuvent avoir un type de données véritablement numérique, ce qui n'est pas un objet JS possible. On pourrait donc dire que nous devrions alors examiner les objets Map dans JS, mais ceux-ci permettent de stocker les clés 13 et "13" séparément, ce que PHP ne permet pas. Et nous ne touchons que la pointe de l'iceberg ici ...
  • PHP sérialise les propriétés protégées et privées des objets, ce qui est non seulement étrange (comment privé est-ce?), Mais sont des concepts qui n'existent pas (encore) dans JS, ou du moins pas de la même manière. Si on implémentait d'une manière ou d'une autre des propriétés privées (matérielles) dans JS, comment une telle non-sérialisation pourrait-elle êtretable à définie une telle propriété privée?
  • JSON est une alternative qui n'est pas spécifique à PHP et ne se soucie pas non plus des classes personnalisées. Si vous pouvez accéder à la source PHP où la sérialisation a eu lieu, changez-la pour produire JSON à la place. PHP offre json_encode pour cela, et JavaScript a JSON.parse pour le décoder. C'est certainement la voie à suivre si vous le pouvez.

Si, avec ces remarques, vous voyez toujours la nécessité d’une fonction d’annulation de la numérotation JS, lisez la suite.

Voici une implémentation JS qui fournit un objet PHP avec des méthodes similaires à celles de l'objet JSON intégré: parse et stringify.

Lorsqu'une entrée de la méthode parse fait référence à une classe, elle vérifie d'abord si vous avez passé une référence à cette classe dans le deuxième argument (facultatif). Sinon, une maquette sera créée pour cette classe (afin d'éviter des effets secondaires indésirables). Dans les deux cas, une instance de cette classe sera créée. Si la chaîne d'entrée spécifie qu'un custom serialization s'est produit, la méthode unserialize sur l'instance de cet objet sera appelée. Vous devez fournir la logique dans cette méthode car la chaîne elle-même ne donne pas d'informations sur la procédure à suivre. Il est seulement connu dans le code PHP que généré cette chaîne.

Cette implémentation prend également en charge les références cycliques. Lorsqu'un tableau associatif s'avère être un tableau séquentiel, un tableau JS est renvoyé.

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 } ));
0
trincot