Je fais un grattoir Web, la plupart des données sur la page Web sont sous forme littérale d'objet JavaScript, par exemple:

// Silly example
var user = {
    name: 'John', 
    surname: 'Doe',
    age: 21,
    family: [
        {
            name: 'Jane',
            surname: 'Doe',
            age: 37
        },
        // ...
    ]
};

Ainsi, lorsque je recherche le contenu de mon application JavaScript, l'objet ci-dessus serait:

"{name: 'John', surname: 'Doe', age: 21, family: [{name: 'Jane', surname: 'Doe', age: 37}]}"

Est-il possible de les analyser avec des objets JavaScript normaux sans utiliser 'eval' ou créer mon propre analyseur? J'ai vu d'autres questions similaires à ce sujet mais les réponses ne sont pas applicables: elles suggèrent toutes JSON.parse() (non applicable) et eval (je ne peux pas l'utiliser pour des raisons de sécurité). Dans ce question, par exemple, toutes les réponses suggèrent eval ou new Function() qui sont essentiellement la même chose.

S'il n'y a pas d'autres moyens, serait-ce une option viable pour convertir le littéral en JSON approprié, puis l'analyser en objet JavaScript?

C'est ce que j'ai essayé en ce moment, cela a fonctionné sur un objet simple mais je ne suis pas sûr que cela fonctionnera partout:

const literal = script.innerText.slice(script.innerText.indexOf('{'), script.innerText.lastIndexOf('}') + 1);
const json = literal.replace(/.*:.*(\".*\"|\'.*\'|\[.*\]|\{.*\}|true|false|[0-9]+).*,/g, (prev) => {
  let parts = prev.split(':');
  let key = '"' + parts.shift().trim() + '"';
  let value = parts.join(':').replace(/'.*'/, (a) => {
    return '"' + a.slice(1, a.length - 1) + '"';
  }).trim();
  return key + ':' + value;
});
const obj = JSON.parse(json);
5
Fr3ddyDev 18 juin 2019 à 15:23

4 réponses

Meilleure réponse

En supposant que vous utilisez un nœud, une solution de contournement simple serait

// scraper.js
const fs = require('fs');
const objectString = myScraper.scrape('example.com');

fs.writeFileSync('./scraped.js', objectString);

// myAppUsingTheData.js
const myObj = require('myAppUsingTheData');

Cependant, exiger implique toujours une évaluation d'une certaine manière. ET vous auriez besoin de processus séparés pour accéder à votre objet. En outre, vous devez en quelque sorte insérer module.exports. Si vous souhaitez analyser uniquement les objets, essayez JSON5

const myObj = JSON5.parse(objectString);
console.log(myObj.name)

L'utilisation de JSON5 vous empêchera efficacement d'exécuter du code malveillant qui n'est pas un objet dans votre application et peut apparemment analyser les clés JSON non cotées.

1
Tom M 18 juin 2019 à 12:49

C'est une démonstration simple de la façon dont vous pouvez utiliser esprima pour obtenir des variables déclarées globalement

"use strict";

const src = `
var user = {
	name: 'John',
	surname: 'Doe',
	age: 21,
	family: [
		{
			name: 'Jane',
			surname: 'Doe',
			age: 37
		},
		// ...
	]
};`;
const src2 = `
var a = [1,2,3], b = true;
var s = "some string";
var o = {a:1}, n = null;
var some = {'realy strange' : {"object":"'literal'"}}
`;

function get_globals(src) {
  return esprima.parse(src).body
    .filter(({type}) => type === "VariableDeclaration") // keep only variables declarations
    .map(({declarations}) => declarations)
    .flat()
    .filter(({type}) => type === "VariableDeclarator")
    .reduce((vars, {id: {name}, init}) => {
      vars[name] = parse(init);
      return vars;
    }, {});
}

console.log(get_globals(src));
console.log(get_globals(src2));

/**
 * Parse expression
 * @param expression
 * @returns {*}
 */
function parse(expression) {
  switch (expression.type) {
    case "ObjectExpression":
      return ObjectExpression(expression);
    case "Identifier":
      return expression.name;
    case "Literal":
      return expression.value;
    case "ArrayExpression":
      return ArrayExpression(expression);
  }
}

/**
 * Parse object expresion
 * @param expression
 * @returns {object}
 */
function ObjectExpression(expression) {
  return expression.properties.reduce((obj, {key, value}) => ({
    ...obj,
    [parse(key)]: parse(value)
  }), {});
}

/**
 * Parse array expression
 * @param expression
 * @returns {*[]}
 */
function ArrayExpression(expression) {
  return expression.elements.map((exp) => parse(exp));
}
<script src="https://unpkg.com/esprima@~4.0/dist/esprima.js"></script>
2
ponury-kostek 18 juin 2019 à 13:47

Pour des données comme celle-ci, vous pourriez être en mesure d'utiliser quelques expressions rationnelles pour convertir en un objet JSON valide.

Voici un exemple ..

Ps. Il peut ne pas être infaillible à 100% pour tous les littéraux d'objets.

var str = "{name: 'John', surname: 'Doe', age: 21, family: [{name: 'Jane', surname: 'Doe', age: 37}]}";

var jstr = str
  .replace(/\'(.*?)\'/g, '"$1"')
  .replace(/([\{ ]*?)([a-z]*?)(\:)/gi, '$1"$2"$3');

var obj = JSON.parse(jstr);

console.log(obj);

Comme l'a souligné @ ponury-kostek, et par moi-même, l'utilisation de regEx peut être limitée. Utiliser une sorte d'analyse syntaxique AST comme Esprima est certainement une bonne idée, surtout si vous utilisez déjà un analyseur AST.

Mais si un analyseur AST est exagéré, une version plus robuste ci-dessous utilisant Javascript pourrait être meilleure. Ps. encore une fois, il peut ne pas être correct à 100%, mais il devrait faire face à la majorité des littéraux Object.

var str = `{
  name: 'John:', surname: 'Doe', age: 21,
  family: [
    {name: '😊Jane\\n\\r', surname: 'Doe', age: 37},
    {'realy:strange indeed' : {"object":"'\\"literal'"}}
  ]
}`;


const objLits = [...':0123456789, \t[]{}\r\n'];

function objParse(src) {
  const input = [...src];
  const output = [];
  let inQ = false, inDQ = false, 
    inEsc = false, inVname = false;
  for (const i of input) {
    if (inEsc) {
      inEsc = false;    
      output.push(i);
    } else if (i === "\\") {
      inEsc = true;
      output.push(i);
    } else if (i === "'" && !inDQ) {
      output.push('"');
      inQ = !inQ;
    } else if (i === '"' && !inQ) {
      output.push('"');
      inDQ = !inDQ;      
    } else if (!inVname & !inQ & !inDQ & !inEsc) {
      if (objLits.includes(i)) {
        output.push(i);
      } else {
        inVname = true;
        output.push('"');
        output.push(i);
      }
    } else if (inVname) {
      if (i === ':') {
        inVname = false;
        output.push('"');
      }
      output.push(i);
    } else {
      output.push(i);
    }
  }
  const ostr = output.join('');
  return JSON.parse(ostr);
}

console.log(objParse(str));
2
Keith 19 juin 2019 à 14:52

Une balise de script peut être ajoutée avec le texte du script:

var JS = `var user = {
    name: 'John', 
    surname: 'Doe',
    age: 21,
    family: [
        {
            name: 'Jane',
            surname: 'Doe',
            age: 37
        },
    ]
};`;

var script = document.createElement('script');
script.textContent = JS
document.head.appendChild(script);

console.log( user )
0
Slai 18 juin 2019 à 16:11