Phân tích chuỗi JSON thành một nguyên mẫu đối tượng cụ thể trong JavaScript


173

Tôi biết cách phân tích một chuỗi JSON và biến nó thành một đối tượng JavaScript. Bạn có thể sử dụng JSON.parse()trong các trình duyệt hiện đại (và IE9 +).

Điều đó thật tuyệt, nhưng làm cách nào tôi có thể lấy Đối tượng JavaScript đó và biến nó thành Đối tượng JavaScript cụ thể (nghĩa là với một nguyên mẫu nhất định)?

Ví dụ: giả sử bạn có:

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

Một lần nữa, tôi không tự hỏi làm thế nào để chuyển đổi một chuỗi JSON thành một Đối tượng JavaScript chung. Tôi muốn biết làm thế nào để chuyển đổi một chuỗi JSON thành Đối tượng "Foo". Đó là, Đối tượng của tôi bây giờ sẽ có chức năng 'kiểm tra' và thuộc tính 'a' và 'b'.

CẬP NHẬT Sau khi thực hiện một số nghiên cứu, tôi nghĩ về điều này ...

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

Công việc vừa ý?

CẬP NHẬT Tháng 5 năm 2017 : Cách thức "hiện đại" để thực hiện việc này là thông qua Object.assign, nhưng chức năng này không khả dụng trong IE 11 hoặc các trình duyệt Android cũ hơn.


Câu trả lời:


124

Các câu trả lời hiện tại chứa rất nhiều mã thư viện hoặc mã thư viện. Điều này là không cần thiết.

  1. Sử dụng JSON.parse('{"a":1}')để tạo một đối tượng đơn giản.

  2. Sử dụng một trong các chức năng được tiêu chuẩn hóa để đặt nguyên mẫu:

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

2
Object.assign không khả dụng trong các trình duyệt cũ hơn bao gồm IE và các trình duyệt Android cũ hơn. kangax.github.io/compat-table/es6/ từ
BMiner

5
Ngoài ra còn có một cảnh báo lớn chống lại việc sử dụng Object.setPrototypeOf(...). developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Khăn

@SimonEpskamp Mã đó không hoạt động. Kiểm tra url của bạn, tham số thứ hai setPrototypeOflà mô tả thuộc tính.
Erik van Velzen

6
Giải pháp với cài đặt nguyên mẫu không hoạt động nếu có một số thuộc tính cũng cần phải có nguyên mẫu. Nói cách khác: nó chỉ giải quyết mức phân cấp dữ liệu đầu tiên.
Vojta

2
kiểm tra giải pháp của tôi dưới đây áp dụng đệ quy Object.assign (..) có thể tự động giải quyết các thuộc tính (với một chút thông tin được cung cấp trước)
vir us

73

Xem một ví dụ dưới đây (ví dụ này sử dụng đối tượng JSON gốc). Những thay đổi của tôi được nhận xét trong VỐN:

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

Tôi cho rằng bạn cũng có thể làm "ngược lại" với điều này. Xây dựng một đối tượng Foo trống và sao chép các thuộc tính từ fooJSON vào đối tượng Foo mới. Cuối cùng, đặt fooJSON để trỏ đến Foo Object.
BMiner

8
Điều này rất nguy hiểm. Nếu obj có một thuộc tính không có trong định nghĩa Foo, bạn sẽ tạo một đối tượng Foo với một thuộc tính ẩn bổ sung mà bạn không biết tên của nó ... Thay vì một vòng lặp tôi sẽ chỉ đơn giản làm: this.a = obj. a và đây.b = obj.b. Hoặc trực tiếp tôi sẽ chuyển "a" và "b" làm tham số: Foo mới (obj.a, obj.b)
Gabriel Llamas

2
Lời khuyên của GagleKas đáng để lắng nghe. (Mặc dù "rất nguy hiểm" là một chút OTT.) Ví dụ ở trên chỉ là để cung cấp cho bạn một ý tưởng. Việc thực hiện đúng sẽ phụ thuộc vào ứng dụng của bạn.
Oliver Moran

11
Bạn có thể muốn bảo vệ bản thân khỏi các thuộc tính nguyên mẫu. for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}
Romain Vergnory

3
@RomainVergnory Để an toàn hơn nữa, tôi chỉ khởi tạo các thuộc tính được tạo trong hàm tạo, cái này thay vì obj : for (var prop in obj) {if (this.hasOwnProperty(prop)) {this[prop] = obj[prop];}}. Điều này giả định rằng bạn mong muốn máy chủ cư trú tất cả các thuộc tính, IMO cũng nên ném nếu obj.hasOwnProperty () không thành công ...
tekHedd

42

Bạn có muốn thêm chức năng tuần tự hóa / giải tuần tự JSON không? Sau đó nhìn vào điều này:

Bạn muốn đạt được điều này:

UML

toJson () là một phương thức bình thường.
fromJson () là một phương thức tĩnh.

Thực hiện :

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

Cách sử dụng :

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

Lưu ý: Nếu bạn muốn, bạn có thể thay đổi tất cả các định nghĩa tài sản như this.title, this.author, vv bằng var title, var authorvv và thêm thu khí cho họ để hoàn thành nghĩa UML.


4
Tôi đồng ý. Việc triển khai này chắc chắn sẽ có hiệu quả, và thật tuyệt ... chỉ một chút thôi và cụ thể cho Đối tượng sách. IMHO, sức mạnh của JS đến từ các nguyên mẫu và khả năng có một số thuộc tính bổ sung nếu bạn muốn. Đó là tất cả những gì tôi đang nói. Tôi đã thực sự tìm kiếm một lớp lót: x .__ proto__ = X.prototype; (mặc dù hiện tại nó không tương thích với trình duyệt IE)
BMiner

4
Đừng quên rằng toJson()phương thức của bạn - bất kể nó có thuộc tính riêng được mã hóa cứng hay sử dụng cho từng thuộc tính - sẽ cần thêm mã thoát dấu gạch chéo ngược cho một số ký tự có thể có trong mỗi thuộc tính chuỗi. (Ví dụ, một tiêu đề sách có thể có dấu ngoặc kép.)
nnnnnn

1
Vâng, tôi biết, câu trả lời của tôi là một ví dụ và câu trả lời tốt nhất cho câu hỏi, nhưng ... thậm chí không phải là một điểm tích cực ... Tôi không biết tại sao tôi lại lãng phí thời gian của mình để giúp đỡ người khác
Gabriel Llamas

7
Những ngày này tôi sẽ sử dụng JSON.stringify()thay vì tự viết toJSon (). Không cần phải phát minh lại bánh xe bây giờ vì tất cả các trình duyệt hiện đại đều hỗ trợ nó.
đá

2
Đồng ý với @skypecakes. Nếu bạn muốn chỉ tuần tự hóa một tập hợp con các thuộc tính, hãy tạo một hằng số các thuộc tính tuần tự hóa. serializable = ['title', 'author', ...]. JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))
Atticus

18

Một bài đăng blog mà tôi thấy hữu ích: Hiểu về Nguyên mẫu JavaScript

Bạn có thể gây rối với thuộc tính __proto__ của Object.

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

Điều này cho phép fooJSON kế thừa nguyên mẫu Foo.

Tôi không nghĩ rằng nó hoạt động trong IE, mặc dù ... ít nhất là từ những gì tôi đã đọc.


2
Thật ra, một thứ như thế là bản năng đầu tiên của tôi.
Oliver Moran

14
Lưu ý rằng __proto__từ lâu đã bị phản đối . Ngoài ra, vì lý do hiệu suất, không nên sửa đổi thuộc tính bên trong [[Prototype]] của một đối tượng đã được tạo (bằng cách đặt __proto__hoặc bằng bất kỳ phương tiện nào khác).
Yu Asakusa

1
Than ôi, không có giải pháp thực sự không bị phản đối nào phức tạp hơn nhiều so với giải đấu này
Wim Leers

Tôi đã thực hiện một số thử nghiệm về hiệu suất thay đổi [[prototype]]và dường như không liên quan trong Chrome. Trong firefox, gọi mới chậm hơn so với sử dụng nguyên mẫu và Object.create là nhanh nhất. Tôi đoán vấn đề với FF là thử nghiệm đầu tiên chậm hơn lần trước, chỉ là thứ tự thực hiện. Trong chrome mọi thứ chạy với tốc độ gần như nhau. Tôi có nghĩa là truy cập tài sản và nvocation của phương pháp. Creatin nhanh hơn với cái mới, nhưng điều đó không quá quan trọng. xem: jsperf.com/prototype-change-test-8874874/1 và: jsperf.com/prototype-changed-method-call
Bogdan Mart

4
Tôi cho rằng những ngày này, người ta sẽ gọi Object.setPrototypeOf(fooJSON, Foo.prototype)thay vì thiết lập fooJSON.__proto__... phải không?
stakx - không còn đóng góp

11

Tôi có thiếu điều gì trong câu hỏi không hoặc tại sao không ai đề cập đến reviverthông số JSON.parsekể từ năm 2011?

Đây là mã đơn giản cho giải pháp hoạt động: 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: Câu hỏi của bạn thật khó hiểu: >> Thật tuyệt, nhưng làm cách nào tôi có thể lấy Đối tượng JavaScript đó và biến nó thành Đối tượng JavaScript cụ thể (nghĩa là với một nguyên mẫu nhất định)? mâu thuẫn với tiêu đề, nơi bạn hỏi về phân tích cú pháp JSON, nhưng đoạn trích dẫn hỏi về thay thế nguyên mẫu đối tượng thời gian chạy JS.


3

Một cách tiếp cận khác có thể được sử dụng Object.create. Là đối số đầu tiên, bạn chuyển nguyên mẫu và đối với mẫu thứ hai, bạn chuyển bản đồ tên thuộc tính cho người mô tả:

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

Tôi muốn thêm một đối số tùy chọn vào hàm tạo và gọi Object.assign(this, obj), sau đó xử lý bất kỳ thuộc tính nào là các đối tượng hoặc mảng của chính các đối tượng:

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

2

Để hoàn thiện, đây là một lớp lót đơn giản mà tôi đã kết thúc (Tôi không có nhu cầu kiểm tra các thuộc tính không thuộc 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

Tôi đã tạo ra một gói gọi là json-khô . Nó hỗ trợ các tham chiếu (tròn) và cả các thể hiện của lớp.

Bạn phải xác định 2 phương thức mới trong lớp của bạn ( toDrytrên nguyên mẫu và unDrydưới dạng phương thức tĩnh), đăng ký lớp ( Dry.registerClass) và tắt đi.


1

Mặc dù, đây không phải là những gì bạn muốn về mặt kỹ thuật, nếu bạn biết trước khi xử lý loại đối tượng bạn muốn xử lý, bạn có thể sử dụng các phương thức gọi / áp dụng nguyên mẫu của đối tượng đã biết.

bạn có thể thay đổi điều này

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

đến đây

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

1

Tôi đã kết hợp các giải pháp mà tôi có thể tìm và biên dịch nó thành một giải pháp chung có thể tự động phân tích một đối tượng tùy chỉnh và tất cả các trường của nó theo cách đệ quy để bạn có thể sử dụng các phương thức nguyên mẫu sau khi khử lưu huỳnh.

Một giả định là bạn đã xác định một tệp đặc biệt chỉ ra loại đó trong mọi đối tượng bạn muốn áp dụng loại tự động ( this.__typetrong ví dụ).

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

sử dụng:

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

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

Hàm gán kiểu (nó hoạt động đệ quy để gán kiểu cho bất kỳ đối tượng lồng nhau nào, nó cũng lặp qua các mảng để tìm bất kỳ đối tượng phù hợp nào):

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

Câu trả lời hiện được chấp nhận không làm việc cho tôi. Bạn cần sử dụng Object.assign () đúng cách:

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

Bạn tạo các đối tượng của lớp này một cách bình thường:

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

Nếu bạn có một chuỗi json bạn cần phân tích cú pháp vào lớp Person, hãy làm như vậy:

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

Câu trả lời của Olivers rất rõ ràng, nhưng nếu bạn đang tìm kiếm một giải pháp trong js góc, tôi đã viết một mô-đun đẹp tên là Angular-jsClass, điều này dễ dàng, có các đối tượng được định nghĩa trong ký hiệu trung thành luôn xấu khi bạn đang nhắm đến một dự án lớn nhưng nói rằng các nhà phát triển phải đối mặt với vấn đề mà BMiner nói chính xác, làm thế nào để tuần tự hóa một json thành các đối tượng ký hiệu nguyên mẫu hoặc hàm tạo

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.