簡體   English   中英

將 JSON 字符串解析為 JavaScript 中的特定 Object 原型

[英]Parse JSON String into a Particular Object Prototype in JavaScript

我知道如何解析 JSON 字符串並將其轉換為 JavaScript Object。 您可以在現代瀏覽器(和 IE9+)中使用JSON.parse() )。

That's great, but how can I take that JavaScript Object and turn it into a particular JavaScript Object (ie with a certain prototype)?

例如,假設您有:

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 Object。 我想知道如何將 JSON 字符串轉換為“Foo” Object。 也就是說,我的 Object 現在應該有一個 function '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 ,但此 function 在 IE 11 或更早版本的 ZE84E30B9390CDB64ABZDB6DB2C 中不可用。

當前的答案包含大量手動或庫代碼。 這不是必需的。

  1. 使用JSON.parse('{"a":1}')創建一個普通的 object。

  2. 使用其中一種標准化函數來設置原型:

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

請參閱下面的示例(此示例使用本機 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

你想添加 JSON 序列化/反序列化功能,對嗎? 然后看看這個:

你想實現這個:

UML

toJson() 是一個普通的方法。
fromJson() 是一個 static 方法。

實施

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 定義。

我發現一篇有用的博文: 了解 JavaScript 原型

你可以弄亂 Object 的 __proto__ 屬性。

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

這允許 fooJSON 繼承 Foo 原型。

不過,我認為這在 IE 中不起作用……至少從我所讀到的內容來看。

我是否遺漏了問題中的某些內容,或者為什么自 2011 年以來沒有人提到JSON.parsereviver參數?

這是有效解決方案的簡單代碼: 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: Your question is confusing: >> That's great, but how can I take that JavaScript Object and turn it into a particular JavaScript Object (ie with a certain prototype)? 與標題相矛盾,您在其中詢問 JSON 解析,但引用的段落詢問 JS 運行時 object 原型替換。

當前接受的答案對我不起作用。 您需要正確使用 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`;
    }
}

您通常創建此 class 的對象:

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

如果你有一個 json 字符串,你需要解析成 Person class,這樣做:

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

另一種方法是使用Object.create 作為第一個參數,您傳遞原型,對於第二個參數,您將屬性名稱的 map 傳遞給描述符:

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

我喜歡向構造函數添加一個可選參數並調用Object.assign(this, obj) ,然后處理作為對象的任何屬性或對象本身的 arrays :

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

為了完整起見,這是我最終得到的一個簡單的單行代碼(我不需要檢查非 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 }'));

我創建了一個名為json-dry的 package 。 它支持(循環)引用以及 class 實例。

You have to define 2 new methods in your class ( toDry on the prototype and unDry as a static method), register the class ( Dry.registerClass ), and off you go.

雖然這在技術上不是您想要的,但如果您事先知道要處理的 object 的類型,則可以使用已知 object 原型的調用/應用方法。

你可以改變這個

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

對此

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

我已經將我能夠找到的解決方案組合起來,並將其編譯成一個通用的解決方案,該解決方案可以自動解析自定義 object 及其所有字段,因此您可以在反序列化后使用原型方法。

一個假設是您定義了一個特殊字段,表明它在每個 object 中的類型,您希望自動應用它的類型(示例中的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(它以遞歸方式為任何嵌套對象分配類型;它還遍歷 arrays 以找到任何合適的對象):

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

這是使用 typescript 和裝飾器的解決方案。

  • 反序列化后對象保留其方法
  • 空對象及其子對象默認初始化

如何使用它:

@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

  • 在每個 object ( __serializedProps ) 中存儲復雜類型屬性的列表

  • 通過類型名稱和__serializableObjects theObject

  • Go 通過 Object theObject.__serializedProps並遞歸遍歷它(從每個序列化屬性的最后一步開始)。 將結果分配給相應的屬性。

  • 使用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 新手(< 10 天),我很高興收到任何輸入/評論/建議。 以下是我到目前為止的一些想法:

它可能更干凈:不幸的是,我沒有找到擺脫@SerializableProp的冗余參數的方法。

它可能對 memory 更友好:在您調用serializeObject()之后,每個 object 存儲__typeName可能會大量炸毀 memory 足跡。 幸運的是,每個__serializedProps只存儲一次 __serializedProps。

它可能對 CPU 更友好:這是我寫過的最低效的代碼。 但是,它只適用於 web 應用程序,所以誰在乎;-) 也許至少應該擺脫遞歸。

幾乎沒有錯誤處理:這是另一天的任務

一個非常簡單的獲得想要的效果的方法是在生成 json 字符串的同時添加一個類型屬性,並在解析字符串的同時使用這個字符串來生成 object:

    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(da) // 1 try { d.method1() // not a function } catch { console.log('not a function') } var e = Object.setPrototypeOf(d, A.prototype) e.method1() // hi

Olivers 的答案非常清楚,但是如果您正在尋找 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM