在没有'eval'的情况下将JavaScript对象文字转换为JavaScript对象

时间:2019-06-18 12:23:14

标签: javascript

我正在制作网络抓取工具,网页上的大多数数据都是JavaScript对象文字形式的,例如:

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

因此,当我在JavaScript应用中搜索内容时,上面的对象将是:

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

是否可以在不使用'eval'或不创建自己的解析器的情况下将其解析为常规JavaScript对象?我看到了与此类似的其他问题,但答案不适用:它们都建议使用JSON.parse()(不适用)和eval(出于安全原因,我不能使用它)。例如,In this question的所有答案都暗示evalnew Function()基本上是同一件事。

如果没有其他方法,将文字转换为正确的JSON,然后将其解析为JavaScript对象是否可行?

这是我现在尝试过的方法,它可以在一个简单的对象上工作,但是我不确定它在任何地方都可以工作:

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);

4 个答案:

答案 0 :(得分:2)

这是一个简单的演示,您如何使用esprima获取全局声明的变量

"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>

答案 1 :(得分:2)

对于这样的数据,您可能可以使用几个正则表达式将其转换为有效的JSON对象。

下面是一个例子。

ps。对于所有对象文字而言,它可能不是100%可靠。

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);

正如@ ponury-kostek指出的那样,并且我自己使用regEx可能受到限制。 使用诸如Esprima之类的AST解析当然是个好主意,尤其是如果您已经在使用AST解析器。

但是,如果AST解析器过大了,下面使用Javascript的更强大的版本可能会更好。附言同样,它可能不是100%正确,但是它应该可以应付大多数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 :(得分:1)

假设您使用节点,一个简单的解决方法是

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

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

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

但是,需求仍然以某种方式涉及评估。并且您需要单独的过程才能访问您的对象。另外,您还需要以某种方式插入module.exports。如果只想解析对象,请尝试JSON5

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

使用JSON5将有效地阻止您运行不是应用程序中对象的恶意代码,并且显然可以解析未引用的JSON密钥。

答案 3 :(得分:0)

可以在脚本文本中添加脚本标签:

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 )