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

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

这是伟大的,但我怎么能采取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浏览器中不可用。

249538 次浏览

请参见下面的示例(此示例使用本机JSON对象)。我的改动用大写字母注释:

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
一篇我觉得有用的博客文章: 理解JavaScript原型 < / p >

你可以扰乱对象的__proto__属性。

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

这允许fooJSON继承Foo原型。

我不认为这在IE中工作,尽管…至少从我所读到的来看是这样。

你想添加JSON序列化/反序列化功能吗?然后看看这个:

你想要实现这个:

UML

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

注意:如果你愿意,你可以通过var titlevar author等改变所有属性定义,如this.titlethis.author等,并向它们添加getter来完成UML定义。

然而,从技术上讲,这并不是你想要的,如果你事先知道你想要处理的对象类型,你可以使用已知对象原型的call/apply方法。

你可以改变这个

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

这个

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

oliver的答案非常清楚,但如果你想在angular js中寻找解决方案,我写了一个很好的模块,叫做angular - jsclass,它可以很容易地解决这个问题,当你的目标是一个大项目时,用文字符号定义对象总是很糟糕,但开发人员会面临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

为了完整起见,这里是我最终得到的一个简单的一行程序(我不需要检查非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 }'));

另一种方法可以使用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);

当前的答案包含大量手卷或库代码。这是不必要的。

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

  2. 使用一个标准化函数来设置原型:

    • Object.assign(new Foo, { a: 1 })
    • Object.setPrototypeOf({ a: 1 }, Foo.prototype)
    • 李< / ul > < / >

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

下面是解决方案的简单代码: https://jsfiddle.net/Ldr2utrr/ < / p >
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运行时对象原型替换

我已经结合了我能够找到的解决方案,并将其编译成一个通用的解决方案,可以自动递归地解析自定义对象及其所有字段,以便在反序列化后使用原型方法。

一个假设是,你定义了一个特殊的字段,在你想自动应用它的类型的每个对象中指出它的类型(在例子中是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);
}

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

你必须在你的类中定义2个新方法(原型上的toDry和静态方法unDry),注册类(Dry.registerClass),然后就可以开始了。

我喜欢在构造函数中添加一个可选参数并调用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));
}
}
}

目前公认的答案对我不起作用。你需要正确使用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


下面是一个使用typescript和decorator的解决方案。

  • 对象在反序列化后保留它们的方法
  • 空对象及其子对象是默认初始化的

如何使用:

@SerializableClass
class SomeClass {
serializedPrimitive: string;


@SerializableProp(OtherSerializedClass)
complexSerialized = new OtherSerializedClass();
}


@SerializableClass
class OtherSerializedClass {
anotherPrimitive: number;


someFunction(): void {
}
}


const obj = new SomeClass();
const json = Serializable.serializeObject(obj);
let deserialized = new SomeClass();
Serializable.deserializeObject(deserialized, JSON.parse(json));
deserialized.complexSerialized.someFunction(); // this works!

它是如何工作的

序列化:

  • 在原型中存储类型名称(__typeName)

  • 使用JSON.stringify和一个替换方法,将__typeName添加到JSON中。

反序列化:

  • 将所有可序列化类型存储在Serializable.__serializableObjects

  • 在每个对象中存储复杂类型属性的列表(__serializedProps)

  • 通过类型名和__serializableObjects初始化对象theObject

  • 遍历theObject.__serializedProps并递归遍历它(从最后一步开始,每个序列化的属性)。将结果分配给according属性。

  • 使用Object.assign来分配所有剩余的基元属性。

代码:

// @Class decorator for serializable objects
export function SerializableClass(targetClass): void {
targetClass.prototype.__typeName = targetClass.name;
Serializable.__serializableObjects[targetClass.name] = targetClass;
}


// @Property decorator for serializable properties
export function SerializableProp(objectType: any) {
return (target: {} | any, name?: PropertyKey): any => {
if (!target.constructor.prototype?.__serializedProps)
target.constructor.prototype.__serializedProps = {};
target.constructor.prototype.__serializedProps[name] = objectType.name;
};
}


export default class Serializable {
public static __serializableObjects: any = {};


private constructor() {
// don't inherit from me!
}


static serializeObject(typedObject: object) {
return JSON.stringify(typedObject, (key, value) => {
if (value) {
const proto = Object.getPrototypeOf(value);
if (proto?.__typeName)
value.__typeName = proto.__typeName;
}
return value;
}
);
}


static deserializeObject(typedObject: object, jsonObject: object): object {
const typeName = typedObject.__typeName;
return Object.assign(typedObject, this.assignTypeRecursion(typeName, jsonObject));
}


private static assignTypeRecursion(typeName, object): object {
const theObject = new Serializable.__serializableObjects[typeName]();
Object.assign(theObject, object);
const props = Object.getPrototypeOf(theObject).__serializedProps;
for (const property in props) {
const type = props[property];
try {
if (type == Array.name) {
const obj = object[property];
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; ++i) {
const arrItem = obj[i];
obj[i] = Serializable.assignTypeRecursion(arrItem.__typeName, arrItem);
}
} else
object[property] = [];
} else
object[property] = Serializable.assignTypeRecursion(type, object[property]);
} catch (e) {
console.error(`${e.message}: ${type}`);
}
}
return theObject;
}
}

< >强评论 因为我是一个完全的js/ts newby (<10天),我很高兴收到任何输入/评论/建议。以下是我目前的一些想法:

它可以更简洁:不幸的是,我没有找到一种方法来消除@SerializableProp的冗余参数。

它可以更加内存友好:在调用serializeObject()之后,每个对象都会存储__typeName,这可能会大量增加内存占用。幸运的是,__serializedProps每个类只存储一次。

它可以对CPU更友好:这是我写过的最低效的代码。但好吧,它只是用于web应用程序,所以谁在乎呢;-)也许人们至少应该摆脱递归。

几乎没有错误处理:那是另一天的任务了

获得理想效果的一个非常简单的方法是在生成json字符串时添加一个type属性,并在解析字符串时使用该字符串来生成对象:

    serialize = function(pObject) {
return JSON.stringify(pObject, (key, value) => {
if (typeof(value) == "object") {
value._type = value.constructor.name;
}
return value;
});
}
    

deSerialize = function(pJsonString) {
return JSON.parse(pJsonString, (key, value) => {
if (typeof(value) == "object" && value._type) {
value = Object.assign(eval('new ' + value._type + '()'), value);
delete value._type;
}
return value;
});
}

这里有一个使用的小例子:

    class TextBuffer {
constructor() {
this.text = "";
}
        

getText = function() {
return this.text;
}
        

setText = function(pText) {
this.text = pText;
}
}
    

let textBuffer = new TextBuffer();
textBuffer.setText("Hallo");
console.log(textBuffer.getText()); // "Hallo"
    

let newTextBuffer = deSerialize(serialize(textBuffer));
console.log(newTextBuffer.getText()); // "Hallo"

class A {
constructor (a) {
this.a = a
}
method1 () {
console.log('hi')
}
}


var b = new A(1)


b.method1() // hi


var c = JSON.stringify(b)


var d = JSON.parse(c)
console.log(d.a) // 1
try {
d.method1() // not a function
} catch {
console.log('not a function')
}


var e = Object.setPrototypeOf(d, A.prototype)


e.method1() // hi