2011-11-13 36 views
24

Tôi có một ứng dụng cho phép người dùng tạo các đối tượng và lưu trữ chúng (trong bảng MySQL, dưới dạng chuỗi) để sử dụng sau này. Các đối tượng có thể là:Biến các chuỗi JSON thành các đối tượng bằng các phương thức

function Obj() { 
    this.label = "new object"; 
} 

Obj.prototype.setLabel = function(newLabel) { 
    this.label = newLabel; 
} 

Nếu tôi sử dụng JSON.stringify trên đối tượng này, tôi sẽ chỉ nhận được thông tin trên Obj.label (đối tượng chuyển đổi thành chuỗi sẽ là một chuỗi như {label: "new object"} Nếu tôi lưu trữ chuỗi này, và muốn. cho phép người dùng của tôi truy xuất đối tượng sau, phương thức setLabel sẽ bị mất

Vì vậy, câu hỏi của tôi là: làm thế nào tôi có thể khởi tạo lại đối tượng để nó giữ các thuộc tính được lưu trữ nhờ JSON.stringify, nhưng cũng được Tôi đã suy nghĩ về một cái gì đó dọc theo "tạo ra một đối tượng trống" và "hợp nhất nó với các thuộc tính của một người được lưu trữ", nhưng Tôi không thể có được nó để làm việc.

+0

Tôi hoan nghênh bạn @Cystack! Khái niệm chuyển các đối tượng Javascript (đó là các thuộc tính dữ liệu thuần túy, không có phương thức nào) được thảo luận ở đây rất nhiều. Tuy nhiên, đây là câu hỏi đầu tiên mà tôi gặp phải thực sự tập trung vào các đối tượng, đó là các đối tượng với các phương thức. Phương thức 'JSON.stringify' bình thường mang đủ cạm bẫy (tham chiếu tuần hoàn, thoát/bộ ký tự, v.v.), do đó hầu hết câu hỏi tập trung vào điều đó và rất đáng buồn tôi có thể tìm thấy về" tự động khôi phục "này. – humanityANDpeace

Trả lời

19

Để thực hiện việc này, bạn sẽ muốn sử dụng chức năng "hồi sinh" khi phân tích cú pháp chuỗi JSON (và hàm "replacer" hoặc hàm toJSON trên nguyên mẫu của hàm tạo của bạn khi tạo nó). Xem Section 15.12.215.12.3 đặc điểm kỹ thuật. Nếu môi trường của bạn chưa hỗ trợ phân tích cú pháp JSON gốc, bạn có thể sử dụng one of Crockford's parsers (Crockford là nhà phát minh JSON), cũng hỗ trợ chức năng "hồi sinh". Dưới đây là một ví dụ riêng biệt đơn giản hoạt động với các trình duyệt tuân theo ES5 (hoặc các thư viện mô phỏng hành vi ES5) (live copy, chạy trong Chrome hoặc Firefox hoặc tương tự), nhưng xem xét ví dụ về giải pháp tổng quát hơn.

// Our constructor function 
function Foo(val) { 
    this.value = val; 
} 
Foo.prototype.nifty = "I'm the nifty inherited property."; 
Foo.prototype.toJSON = function() { 
    return "/Foo(" + this.value + ")/"; 
}; 

// An object with a property, `foo`, referencing an instance 
// created by that constructor function, and another `bar` 
// which is just a string 
var obj = { 
    foo: new Foo(42), 
    bar: "I'm bar" 
}; 

// Use it 
display("obj.foo.value = " + obj.foo.value); 
display("obj.foo.nifty = " + obj.foo.nifty); 
display("obj.bar = " + obj.bar); 

// Stringify it with a replacer: 
var str = JSON.stringify(obj); 

// Show that 
display("The string: " + str); 

// Re-create it with use of a "reviver" function 
var obj2 = JSON.parse(str, function(key, value) { 
    if (typeof value === "string" && 
     value.substring(0, 5) === "/Foo(" && 
     value.substr(-2) == ")/" 
    ) { 
    return new Foo(value.substring(5, value.length - 2)); 
    } 
    return value; 
}); 

// Use the result 
display("obj2.foo.value = " + obj2.foo.value); 
display("obj2.foo.nifty = " + obj2.foo.nifty); 
display("obj2.bar = " + obj2.bar); 

Lưu ý toJSON trên Foo.prototype, và các chức năng chúng tôi vượt qua thành JSON.parse.

Vấn đề ở đó, mặc dù, là trình thu thập được kết hợp chặt chẽ với nhà xây dựng Foo. Thay vào đó, bạn có thể áp dụng một khuôn khổ chung trong mã của bạn, ở đó bất kỳ hàm xây dựng nào cũng có thể hỗ trợ hàm fromJSON (hoặc tương tự) và bạn có thể chỉ sử dụng một trình hồi sinh tổng quát.

Dưới đây là một ví dụ về một người bình phục khái quát hóa mà tìm kiếm một tài sản ctor và một tài sản data, và kêu gọi ctor.fromJSON nếu tìm thấy, đi qua trong toàn bộ giá trị nó nhận được (live example):

// A generic "smart reviver" function. 
// Looks for object values with a `ctor` property and 
// a `data` property. If it finds them, and finds a matching 
// constructor that has a `fromJSON` property on it, it hands 
// off to that `fromJSON` fuunction, passing in the value. 
function Reviver(key, value) { 
    var ctor; 

    if (typeof value === "object" && 
     typeof value.ctor === "string" && 
     typeof value.data !== "undefined") { 
    ctor = Reviver.constructors[value.ctor] || window[value.ctor]; 
    if (typeof ctor === "function" && 
     typeof ctor.fromJSON === "function") { 
     return ctor.fromJSON(value); 
    } 
    } 
    return value; 
} 
Reviver.constructors = {}; // A list of constructors the smart reviver should know about 

Để tránh phải lặp lại logic thông thường trong toJSONfromJSON chức năng, bạn có thể có phiên bản generic:

// A generic "toJSON" function that creates the data expected 
// by Reviver. 
// `ctorName` The name of the constructor to use to revive it 
// `obj`  The object being serialized 
// `keys`  (Optional) Array of the properties to serialize, 
//    if not given then all of the objects "own" properties 
//    that don't have function values will be serialized. 
//    (Note: If you list a property in `keys`, it will be serialized 
//    regardless of whether it's an "own" property.) 
// Returns: The structure (which will then be turned into a string 
//    as part of the JSON.stringify algorithm) 
function Generic_toJSON(ctorName, obj, keys) { 
    var data, index, key; 

    if (!keys) { 
    keys = Object.keys(obj); // Only "own" properties are included 
    } 

    data = {}; 
    for (index = 0; index < keys.length; ++index) { 
    key = keys[index]; 
    data[key] = obj[key]; 
    } 
    return {ctor: ctorName, data: data}; 
} 

// A generic "fromJSON" function for use with Reviver: Just calls the 
// constructor function with no arguments, then applies all of the 
// key/value pairs from the raw data to the instance. Only useful for 
// constructors that can be reasonably called without arguments! 
// `ctor`  The constructor to call 
// `data`  The data to apply 
// Returns: The object 
function Generic_fromJSON(ctor, data) { 
    var obj, name; 

    obj = new ctor(); 
    for (name in data) { 
    obj[name] = data[name]; 
    } 
    return obj; 
} 

Ưu điểm ở đây là bạn de fer để thực hiện một "loại" cụ thể (cho thiếu một thuật ngữ tốt hơn) cho nó serializes và deserializes. Vì vậy, bạn có thể có một "loại" mà chỉ sử dụng Generics:

// `Foo` is a constructor function that integrates with Reviver 
// but doesn't need anything but the generic handling. 
function Foo() { 
} 
Foo.prototype.nifty = "I'm the nifty inherited property."; 
Foo.prototype.spiffy = "I'm the spiffy inherited property."; 
Foo.prototype.toJSON = function() { 
    return Generic_toJSON("Foo", this); 
}; 
Foo.fromJSON = function(value) { 
    return Generic_fromJSON(Foo, value.data); 
}; 
Reviver.constructors.Foo = Foo; 

... hoặc một trong đó, vì lý do gì, đã làm một cái gì đó nhiều tùy chỉnh:

// `Bar` is a constructor function that integrates with Reviver 
// but has its own custom JSON handling for whatever reason. 
function Bar(value, count) { 
    this.value = value; 
    this.count = count; 
} 
Bar.prototype.nifty = "I'm the nifty inherited property."; 
Bar.prototype.spiffy = "I'm the spiffy inherited property."; 
Bar.prototype.toJSON = function() { 
    // Bar's custom handling *only* serializes the `value` property 
    // and the `spiffy` or `nifty` props if necessary. 
    var rv = { 
    ctor: "Bar", 
    data: { 
     value: this.value, 
     count: this.count 
    } 
    }; 
    if (this.hasOwnProperty("nifty")) { 
    rv.data.nifty = this.nifty; 
    } 
    if (this.hasOwnProperty("spiffy")) { 
    rv.data.spiffy = this.spiffy; 
    } 
    return rv; 
}; 
Bar.fromJSON = function(value) { 
    // Again custom handling, for whatever reason Bar doesn't 
    // want to serialize/deserialize properties it doesn't know 
    // about. 
    var d = value.data; 
     b = new Bar(d.value, d.count); 
    if (d.spiffy) { 
    b.spiffy = d.spiffy; 
    } 
    if (d.nifty) { 
    b.nifty = d.nifty; 
    } 
    return b; 
}; 
Reviver.constructors.Bar = Bar; 

Và dưới đây là cách chúng tôi có thể kiểm tra rằng FooBar công việc như mong đợi (live copy):

// An object with `foo` and `bar` properties: 
var before = { 
    foo: new Foo(), 
    bar: new Bar("testing", 42) 
}; 
before.foo.custom = "I'm a custom property"; 
before.foo.nifty = "Updated nifty"; 
before.bar.custom = "I'm a custom property"; // Won't get serialized! 
before.bar.spiffy = "Updated spiffy"; 

// Use it 
display("before.foo.nifty = " + before.foo.nifty); 
display("before.foo.spiffy = " + before.foo.spiffy); 
display("before.foo.custom = " + before.foo.custom + " (" + typeof before.foo.custom + ")"); 
display("before.bar.value = " + before.bar.value + " (" + typeof before.bar.value + ")"); 
display("before.bar.count = " + before.bar.count + " (" + typeof before.bar.count + ")"); 
display("before.bar.nifty = " + before.bar.nifty); 
display("before.bar.spiffy = " + before.bar.spiffy); 
display("before.bar.custom = " + before.bar.custom + " (" + typeof before.bar.custom + ")"); 

// Stringify it with a replacer: 
var str = JSON.stringify(before); 

// Show that 
display("The string: " + str); 

// Re-create it with use of a "reviver" function 
var after = JSON.parse(str, Reviver); 

// Use the result 
display("after.foo.nifty = " + after.foo.nifty); 
display("after.foo.spiffy = " + after.foo.spiffy); 
display("after.foo.custom = " + after.foo.custom + " (" + typeof after.foo.custom + ")"); 
display("after.bar.value = " + after.bar.value + " (" + typeof after.bar.value + ")"); 
display("after.bar.count = " + after.bar.count + " (" + typeof after.bar.count + ")"); 
display("after.bar.nifty = " + after.bar.nifty); 
display("after.bar.spiffy = " + after.bar.spiffy); 
display("after.bar.custom = " + after.bar.custom + " (" + typeof after.bar.custom + ")"); 

display("(Note that after.bar.custom is undefined because <code>Bar</code> specifically leaves it out.)"); 
+0

gợi ý thú vị, nhưng điều này chỉ hoạt động với lớp Foo của bạn. Nếu bạn nhìn vào Obj() của tôi, tôi không thể sử dụng nó để hồi sinh đối tượng, vì '.abel'property sẽ bị mất. – Cystack

+0

xem điều này dựa trên ví dụ của bạn: http://jsfiddle.net/cBBd4/ thuộc tính tiện lợi bị mất – Cystack

+0

@Cystack: Trên thực tế, tôi đã thêm một bộ thu hồi tổng quát (và chỉ thay thế nó bằng một cái tốt hơn). Re fiddle của bạn: Chắc chắn, đó là chức năng 'toJSON' của đối tượng' Foo', cần phải lưu các thuộc tính thích hợp (nhiều đối tượng sẽ có các thuộc tính không được tuần tự hóa). Bạn cũng có thể làm một điều tổng quát, như được hiển thị trong bản cập nhật mới nhất của tôi. –

0

Cố gắng sử dụng toString trên phương pháp.

Cập nhật:

Lặp lại các phương pháp trong obj và lưu trữ chúng dưới dạng chuỗi và sau đó khởi tạo chúng bằng hàm mới.

storedFunc = Obj.prototype.setLabel.toString(); 
Obj2.prototype['setLabel'] = new Function("return (" + storedFunc + ")")(); 
+0

ý của bạn là gì? sao cho tôi lưu trữ phương thức như một chuỗi trong đối tượng?đánh bại mục đích của nguyên mẫu, phải không? – Cystack

+0

không nhất thiết phải IN đối tượng, trong DB của bạn, chỉ cần thêm một bảng cho các phương thức và đối tượng mà chúng thuộc về, gửi lại json bình thường cho đối tượng và sau đó là chuỗi phương thức và thêm chúng vào đối tượng .. –

+0

Vì OP hỏi về các đối tượng có các phương thức, khi nói đến JSON, sau đó tôi nghĩ ý tưởng sử dụng 'replacer' để lưu trữ các phương thức trong một String (có thể nên được thoát và hoặc base64 để không ghi chú ký hiệu JSON) và làm sống lại các phương thức một lát sau. Đề xuất này có thể không được xem xét (tốt, tức là có các phương thức được lưu trữ trong DB) nhưng tôi nghĩ nó liên kết với OP và câu trả lời làm dồi dào làm giàu không gian giải pháp, @ j-a cảm ơn bạn! – humanityANDpeace

1

Cho đến khi tôi biết, điều này có nghĩa là di chuyển ra khỏi JSON; bây giờ bạn đang tùy chỉnh nó, và do đó bạn có tất cả các cơn đau đầu tiềm ẩn đòi hỏi. Ý tưởng của JSON là chỉ bao gồm dữ liệu, không phải mã, để tránh tất cả các vấn đề bảo mật mà bạn nhận được khi bạn cho phép mã được đưa vào. Cho phép mã có nghĩa là bạn phải sử dụng eval để chạy mã và eval là xấu.

+0

Tôi không muốn đặt mã trong JSON, tôi chỉ đề cập đến thực tế là nó không lưu trữ các phương thức theo bất kỳ cách nào. Nhưng tôi mở cửa cho bất kỳ giải pháp nào! – Cystack

+0

* "Theo như tôi biết, điều này có nghĩa là di chuyển ra khỏi JSON ..." * Không thực sự, các chức năng hồi sinh đã tồn tại trong một thời gian dài. –

+0

Tôi đoán tôi không hiểu bản chất của câu hỏi. Một chức năng phục hồi cho phép bạn sử dụng một hàm hiện có (không phải một trong JSON) để làm việc với dữ liệu khi nó được đọc, bao gồm xây dựng một đối tượng và thêm mã của riêng bạn. Điều này có nghĩa là bạn có thể lưu trữ các tên thuộc tính gắn cờ những chức năng nào sẽ được thêm vào đối tượng được hoàn nguyên. Nhưng nó không lưu trữ mã trong JSON. Trừ khi tôi đang thiếu một cái gì đó ... nếu tôi, xin vui lòng cho tôi giác ngộ. – dnuttle

5

Bạn thực sự có thể tạo một phiên bản trống và sau đó hợp nhất cá thể với dữ liệu. Tôi khuyên bạn nên sử dụng chức năng thư viện để dễ sử dụng (như jQuery.extend).

Bạn có một số lỗi mặc dù (function ... = function(...) và JSON yêu cầu các phím được bao quanh bởi ").

http://jsfiddle.net/sc8NU/1/

var data = '{"label": "new object"}'; // JSON 
var inst = new Obj;     // empty instance 
jQuery.extend(inst, JSON.parse(data)); // merge 

Lưu ý rằng việc sáp nhập như thế này đặt thuộc tính trực tiếp, vì vậy nếu setLabel đang làm một số công cụ kiểm tra, điều này sẽ không được thực hiện theo cách này.

+0

rất tốt! đây là điều gần nhất với những gì tôi nghĩ. Bây giờ nhược điểm duy nhất là tôi đã dành nhiều giờ không bao gồm jQuery vì nhiều lý do khác nhau, tôi sẽ ghét bản thân mình để bao gồm nó chỉ cho rằng^^ Tôi sẽ xem xét mã .extend chức năng mặc dù – Cystack

+0

@Cystack: Có rất nhiều 'mở rộng' các hàm có sẵn trong nhiều thư viện; bạn cũng có thể cho vay và sao chép nó vào dự án của bạn. Tệp underscore.js không có phụ thuộc: http://documentcloud.github.com/underscore/underscore.js. – pimvdb

+2

@Cystack & pimvdb: Vấn đề ở đây là nó giả định rằng 'Obj' * có thể * được gọi không có đối số, và như pimvdb chỉ ra, nó bỏ qua API của kiểu (có thể là tốt hay không). Theo quan điểm của tôi, tốt hơn hết là bạn nên trì hoãn các quyết định đó cho chính loại đó (dĩ nhiên, có thể, vui vẻ sử dụng 'extend' nếu appropraite). –

0

Bạn sẽ phải viết phương thức xâu chuỗi của riêng bạn để lưu trữ các hàm dưới dạng thuộc tính bằng cách chuyển đổi chúng thành chuỗi bằng phương thức toString.

1

Nếu bạn muốn sử dụng setters của obj:

0.123.
Obj.createFromJSON = function(json){ 
    if(typeof json === "string") // if json is a string 
     json = JSON.parse(json); // we convert it to an object 
    var obj = new Obj(), setter; // we declare the object we will return 
    for(var key in json){ // for all properties 
     setter = "set"+key[0].toUpperCase()+key.substr(1); // we get the name of the setter for that property (e.g. : key=property => setter=setProperty 
     // following the OP's comment, we check if the setter exists : 
     if(setter in obj){ 
     obj[setter](json[key]); // we call the setter 
     } 
     else{ // if not, we set it directly 
     obj[key] = json[key]; 
     } 
    } 
    return obj; // we finally return the instance 
}; 

Điều này yêu cầu lớp học của bạn phải có người định cư cho tất cả các thuộc tính của nó. Phương pháp này là tĩnh, vì vậy bạn có thể sử dụng như thế này:

var instance = Obj.createFromJSON({"label":"MyLabel"}); 
var instance2 = Obj.createFromJSON('{"label":"MyLabel"}'); 
+0

+1 Giải pháp hay; có lẽ trong trường hợp 'setXXX' không có sẵn, chỉ cần đặt nó trực tiếp (vì khá nhiều thuộc tính có thể không cần một hàm setter riêng biệt). – pimvdb

+0

@pimvdb OK, tôi đã tính đến nhận xét của bạn trong lần chỉnh sửa cuối cùng. – fflorent

0

JavaScript là prototype based programming language đó là ngôn ngữ không giai cấp nơi định hướng đối tượng đạt được bằng quá trình nhân bản đối tượng hiện đóng vai trò như nguyên mẫu.

serializing JSON sẽ được xem xét bất kỳ phương pháp, ví dụ nếu bạn có một đối tượng

var x = { 
    a: 4 
    getText: function() { 
     return x.a; 
    } 
}; 

Bạn sẽ nhận được chỉ { a:4 } nơi phương pháp getText được bỏ qua bởi các serializer.

Tôi chạy vào rắc rối này cùng một năm trở lại và tôi phải duy trì một lớp helper riêng cho từng đối tượng tên miền của tôi và sử dụng $.extend() nó để tôi deserialized đối tượng khi cần thiết, chỉ cần giống như có phương pháp để cơ sở lớp cho các đối tượng miền.

Các vấn đề liên quan