将JSON字符串解析为JavaScript中的特定对象原型


173

我知道如何解析JSON字符串并将其转换为JavaScript对象。您可以JSON.parse()在现代浏览器(和IE9 +)中使用。

太好了,但是我怎样才能把那个JavaScript对象变成一个特定的 JavaScript对象(即具有特定的原型)呢?

例如,假设您有:

function Foo()
{
   this.a = 3;
   this.b = 2;
   this.test = function() {return this.a*this.b;};
}
var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse({"a":4, "b": 3});
//Something to convert fooJSON into a Foo Object
//....... (this is what I am missing)
alert(fooJSON.test() ); //Prints 12

再次说明,我不奇怪如何将JSON字符串转换为通用JavaScript对象。我想知道如何将JSON字符串转换为“ Foo”对象。也就是说,我的对象现在应该具有函数“ test”以及属性“ a”和“ b”。

更新 经过研究后,我想到了...

Object.cast = function cast(rawObj, constructor)
{
    var obj = new constructor();
    for(var i in rawObj)
        obj[i] = rawObj[i];
    return obj;
}
var fooJSON = Object.cast({"a":4, "b": 3}, Foo);

那行得通吗?

更新2017年5月:“现代”方式是通过Object.assign,但此功能在IE 11或更旧的Android浏览器中不可用。


Answers:


121

当前的答案包含很多手动或库代码。这不是必需的。

  1. 使用JSON.parse('{"a":1}')创建一个普通的对象。

  2. 使用标准化功能之一设置原型:

    • Object.assign(new Foo, { a: 1 })
    • Object.setPrototypeOf({ a: 1 }, Foo.prototype)

2
Object.assign在较旧的浏览器(包括IE和较旧的Android浏览器)中不可用。kangax.github.io/compat-table/es6/…–
BMiner

5
还有一个强烈警告不要使用Object.setPrototypeOf(...)developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/…–
christo8989

@SimonEpskamp该代码不起作用。检查您的网址,第二个参数setPrototypeOf是属性描述符。
Erik van Velzen

6
如果某些属性也需要具有原型,则设置原型的解决方案将不起作用。换句话说:它只解决数据层次结构的第一级。
Vojta

2
在下面检查我的解决方案,该解决方案递归地应用Object.assign(..),该解决方案可以自动解析属性(预先提供了一些信息)
vir us

73

请参见下面的示例(此示例使用本机JSON对象)。在CAPITALS中评论了我的更改:

function Foo(obj) // CONSTRUCTOR CAN BE OVERLOADED WITH AN OBJECT
{
    this.a = 3;
    this.b = 2;
    this.test = function() {return this.a*this.b;};

    // IF AN OBJECT WAS PASSED THEN INITIALISE PROPERTIES FROM THAT OBJECT
    for (var prop in obj) this[prop] = obj[prop];
}

var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6

// INITIALISE A NEW FOO AND PASS THE PARSED JSON OBJECT TO IT
var fooJSON = new Foo(JSON.parse('{"a":4,"b":3}'));

alert(fooJSON.test() ); //Prints 12

我想您也可以做到这一点。构造一个空白的Foo对象,然后将属性从fooJSON复制到新的Foo对象中。最后,将fooJSON设置为指向Foo对象。
BMiner

8
这是非常危险的。如果obj的属性不在Foo定义中,则将创建一个Foo对象,该对象带有一个额外的隐藏属性,而您不知道它的名称...而不是循环,我将简单地执行此操作:this.a = obj。 a和this.b =obj.b。或者直接将“ a”和“ b”作为参数传递:new Foo(obj.a,obj.b)
Gabriel Llamas

2
GagleKas的建议值得一听。(尽管“非常危险”有点OTT。)上面的示例只是为了给您一个想法。正确的实现将取决于您的应用程序。
奥利弗·莫兰

11
您可能想保护自己免受原型属性的影响。for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}
Romain Vergnory 2014年

3
@RomainVergnory为了更加安全,在构造函数中创建我只初始化属性,这不是OBJ: for (var prop in obj) {if (this.hasOwnProperty(prop)) {this[prop] = obj[prop];}}。假设您希望服务器填充所有属性,如果obj.hasOwnProperty()失败,IMO也应该抛出...
tekHedd 16-10-26

42

您是否要添加JSON序列化/反序列化功能,对吗?然后看一下:

您想要实现以下目标:

统一语言

toJson()是一种常规方法。
fromJson()是静态方法。

实施方式

var Book = function (title, author, isbn, price, stock){
    this.title = title;
    this.author = author;
    this.isbn = isbn;
    this.price = price;
    this.stock = stock;

    this.toJson = function (){
        return ("{" +
            "\"title\":\"" + this.title + "\"," +
            "\"author\":\"" + this.author + "\"," +
            "\"isbn\":\"" + this.isbn + "\"," +
            "\"price\":" + this.price + "," +
            "\"stock\":" + this.stock +
        "}");
    };
};

Book.fromJson = function (json){
    var obj = JSON.parse (json);
    return new Book (obj.title, obj.author, obj.isbn, obj.price, obj.stock);
};

用法

var book = new Book ("t", "a", "i", 10, 10);
var json = book.toJson ();
alert (json); //prints: {"title":"t","author":"a","isbn":"i","price":10,"stock":10}

var book = Book.fromJson (json);
alert (book.title); //prints: t

注意:如果需要,可以通过,等等更改所有属性定义,例如this.titlethis.author等等var titlevar author并向其添加getter以完成UML定义。


4
我同意。这个实现肯定会起作用,而且很棒……只需要一点点罗and和特定于Book对象。恕我直言,JS的强大功能来自原型,如果需要,还可以具有一些其他属性。这就是我要说的。我真的在寻找单线:x .__ proto__ = X.prototype; (尽管目前与IE浏览器不兼容)
BMiner

4
别忘了,您的toJson()方法(无论是具有单独的属性硬编码还是针对每个属性使用)都将需要为每个字符串属性中的某些字符添加反斜杠转义码。(例如,书名可能带有引号。)
nnnnnn

1
是的,我知道,我的回答是一个例子,也是该问题的最佳答案,但是……甚至还不是积极的观点……我不知道为什么我会浪费时间帮助他人
Gabriel Llamas

7
这些天来,我会用JSON.stringify()自己代替写toJSon()。现在所有现代浏览器都支持它,因此无需重新发明轮子。

2
同意@skypecakes。如果只想序列化属性的子集,请创建一个可序列化属性的常量。serializable = ['title', 'author', ...]JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))
阿提克斯

18

我发现有用的博客文章: 了解JavaScript原型

您可以弄乱对象的__proto__属性。

var fooJSON = jQuery.parseJSON({"a":4, "b": 3});
fooJSON.__proto__ = Foo.prototype;

这允许fooJSON继承Foo原型。

我认为这在IE中不起作用,至少从我所读的内容来看。


2
其实,那是我的本能。
奥利弗·莫兰

14
请注意,__proto__早已不推荐使用。此外,出于性能原因,建议不要修改已创建对象的[[Prototype]]内部属性(通过设置__proto__或任何其他方式)。
浅草优

1
las,实际上未弃用的解决方案都没有比这更复杂的了……
Wim Leers

我已经对变更的性能进行了一些测试,[[prototype]]在Chrome中似乎无关紧要。在firefox中,调用new比使用原型慢,而Object.create则最快。我想FF的问题在于,第一个测试比最后一个测试慢,只是执行顺序很重要。在chrome中,一切都以几乎相同的速度运行。我的意思是属性访问和方法调用。creatin的更新速度更快,但这并不重要。请参阅: jsperf.com/prototype-change-test-8874874/1和:jsperf.com/prototype-changed-method-call
Bogdan Mart

4
我想这些天,有人会打电话Object.setPrototypeOf(fooJSON, Foo.prototype)代替设定fooJSON.__proto__...对吧?
stakx-不再贡献

11

我是否在问题中遗漏了一些东西,或者为什么自2011年以来没有人提及reviver参数JSON.parse

这是有效的解决方案的简单代码:https : //jsfiddle.net/Ldr2utrr/

function Foo()
{
   this.a = 3;
   this.b = 2;
   this.test = function() {return this.a*this.b;};
}


var fooObj = new Foo();
alert(fooObj.test() ); //Prints 6
var fooJSON = JSON.parse(`{"a":4, "b": 3}`, function(key,value){
if(key!=="") return value; //logic of course should be more complex for handling nested objects etc.
  let res = new Foo();
  res.a = value.a;
  res.b = value.b;
  return res;
});
// Here you already get Foo object back
alert(fooJSON.test() ); //Prints 12

PS:您的问题令人困惑:>> 太好了,但是我如何才能将该JavaScript对象转换为特定的JavaScript对象(即具有特定原型)? 与标题(您在其中询问JSON解析)相矛盾,但引用的段落询问有关JS运行时对象原型替换的信息。


3

另一种方法是使用Object.create。作为第一个参数,您传递原型,对于第二个参数,您传递属性名称映射到描述符:

function SomeConstructor() {
  
};

SomeConstructor.prototype = {
  doStuff: function() {
      console.log("Some stuff"); 
  }
};

var jsonText = '{ "text": "hello wrold" }';
var deserialized = JSON.parse(jsonText);

// This will build a property to descriptor map
// required for #2 argument of Object.create
var descriptors = Object.keys(deserialized)
  .reduce(function(result, property) {
    result[property] = Object.getOwnPropertyDescriptor(deserialized, property);
  }, {});

var obj = Object.create(SomeConstructor.prototype, descriptors);


3

我喜欢在构造函数中添加一个可选参数并调用Object.assign(this, obj),然后处理对象本身或对象数组的任何属性:

constructor(obj) {
    if (obj != null) {
        Object.assign(this, obj);
        if (this.ingredients != null) {
            this.ingredients = this.ingredients.map(x => new Ingredient(x));
        }
    }
}

2

为了完整起见,我得到了一个简单的单线代码(我不需要检查非Foo属性):

var Foo = function(){ this.bar = 1; };

// angular version
var foo = angular.extend(new Foo(), angular.fromJson('{ "bar" : 2 }'));

// jquery version
var foo = jQuery.extend(new Foo(), jQuery.parseJSON('{ "bar" : 3 }'));

2

我创建了一个名为json-dry的包。它支持(循环)引用以及类实例。

您必须在您的类中定义2个新方法(toDry在原型上并unDry作为静态方法),注册类(Dry.registerClass),然后开始使用。


1

虽然这并不是您想要的技术,但是如果您事先知道要处理的对象的类型,则可以使用已知对象原型的call / apply方法。

你可以改变这个

alert(fooJSON.test() ); //Prints 12

对此

alert(Foo.prototype.test.call(fooJSON); //Prints 12

1

我将能够找到的解决方案组合在一起,并将其编译为一个通用的解决方案,该解决方案可以自动解析自定义对象及其所有字段,以便您可以在反序列化之后使用原型方法。

一种假设是,您定义了一个特殊的文件,该文件指示您要自动应用其类型的每个对象的类型(this.__type在示例中)。

function Msg(data) {
    //... your init code
    this.data = data //can be another object or an array of objects of custom types. 
                     //If those objects defines `this.__type', their types will be assigned automatically as well
    this.__type = "Msg"; // <- store the object's type to assign it automatically
}

Msg.prototype = {
    createErrorMsg: function(errorMsg){
        return new Msg(0, null, errorMsg)
    },
    isSuccess: function(){
        return this.errorMsg == null;
    }
}

用法:

var responseMsg = //json string of Msg object received;
responseMsg = assignType(responseMsg);

if(responseMsg.isSuccess()){ // isSuccess() is now available
      //furhter logic
      //...
}

类型分配函数(它为所有嵌套对象分配类型的递归工作;它还会遍历数组以查找任何合适的对象):

function assignType(object){
    if(object && typeof(object) === 'object' && window[object.__type]) {
        object = assignTypeRecursion(object.__type, object);
    }
    return object;
}

function assignTypeRecursion(type, object){
    for (var key in object) {
        if (object.hasOwnProperty(key)) {
            var obj = object[key];
            if(Array.isArray(obj)){
                 for(var i = 0; i < obj.length; ++i){
                     var arrItem = obj[i];
                     if(arrItem && typeof(arrItem) === 'object' && window[arrItem.__type]) {
                         obj[i] = assignTypeRecursion(arrItem.__type, arrItem);
                     }
                 }
            } else  if(obj && typeof(obj) === 'object' && window[obj.__type]) {
                object[key] = assignTypeRecursion(obj.__type, obj);
            }
        }
    }
    return Object.assign(new window[type](), object);
}

0

当前接受的答案不适用于我。您需要正确使用Object.assign():

class Person {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    greet(){
        return `hello my name is ${ this.name } and i am ${ this.age } years old`;
    }
}

您通常创建此类的对象:

let matt = new Person('matt', 12);
console.log(matt.greet()); // prints "hello my name is matt and i am 12 years old"

如果您有json字符串,则需要解析到Person类中,如下所示:

let str = '{"name": "john", "age": 15}';
let john = JSON.parse(str); // parses string into normal Object type

console.log(john.greet()); // error!!

john = Object.assign(Person.prototype, john); // now john is a Person type
console.log(john.greet()); // now this works

-3

Olivers的答案很明确,但是如果您正在寻找使用Angular js的解决方案,我已经编写了一个名为Angular-jsClass的不错的模块,该模块可以轻松实现这一点,当您针对大型项目时,使用以lital表示法定义的对象总是不好的但是说开发人员面临的问题正是BMiner所说的,如何将json序列化为原型或构造函数表示法对象

var jone = new Student();
jone.populate(jsonString); // populate Student class with Json string
console.log(jone.getName()); // Student Object is ready to use

https://github.com/imalhasaranga/Angular-JSClass

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.