แยกสตริง 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" นั่นคือตอนนี้วัตถุของฉันควรมีฟังก์ชั่น 'ทดสอบ' และคุณสมบัติ 'a' และ 'b'

UPDATE หลังจากทำวิจัยแล้วฉันคิดถึงสิ่งนี้ ...

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 : วิธีการ "ทันสมัย" ในการทำเช่นนี้ผ่านObject.assignแต่ฟังก์ชั่นนี้ไม่สามารถใช้ได้ในเบราว์เซอร์ IE 11 ขึ้นไป


คำตอบ:


124

คำตอบปัจจุบันมีโค้ดรีดด้วยมือหรือไลบรารีจำนวนมาก สิ่งนี้ไม่จำเป็น

  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/en-US/docs/Web/JavaScript/Reference/
......

@SimonEpskamp รหัสนั้นใช้ไม่ได้ ตรวจสอบ url ของคุณพารามิเตอร์ตัวที่สองให้setPrototypeOfเป็น descriptors คุณสมบัติ
Erik van Velzen

6
โซลูชันที่มีการตั้งค่าต้นแบบไม่ทำงานหากมีคุณสมบัติบางอย่างที่ต้องมีต้นแบบ กล่าวอีกนัยหนึ่ง: มันจะแก้ไขลำดับชั้นของข้อมูลในระดับแรกเท่านั้น
Vojta

2
ตรวจสอบโซลูชันของฉันด้านล่างซึ่งใช้ Object.assign (.. ) แบบเรียกซ้ำที่สามารถแก้ไขคุณสมบัติ (พร้อมข้อมูลเล็กน้อยที่ให้ไว้ล่วงหน้า) โดยอัตโนมัติ
vir us

73

ดูตัวอย่างด้านล่าง (ตัวอย่างนี้ใช้วัตถุ 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

ฉันคิดว่าคุณสามารถทำ "ตรงกันข้าม" ของสิ่งนี้ได้เช่นกัน สร้าง Foo Object ที่ว่างเปล่าและคัดลอกคุณสมบัติจาก fooJSON ไปยัง Foo Object ใหม่ ในที่สุดตั้ง fooJSON ให้ชี้ไปที่วัตถุ Foo
BMiner

8
มันอันตรายมาก หาก obj มีคุณสมบัติที่ไม่ได้อยู่ในคำจำกัดความของ Foo คุณจะสร้างวัตถุ Foo ที่มีคุณสมบัติพิเศษที่ซ่อนอยู่ซึ่งคุณไม่รู้จักชื่อ ... แทนที่จะวนซ้ำฉันจะทำอย่างนี้: this.a = obj a และ this.b = obj.b หรือโดยตรงฉันจะผ่าน "a" และ "b" เป็นพารามิเตอร์: ใหม่ Foo (obj.a, obj.b)
Gabriel Llamas

2
คำแนะนำของ GagleKas นั้นคุ้มค่าที่จะฟัง (แม้ว่า "อันตรายมาก" เป็น OTT เล็กน้อย) ตัวอย่างด้านบนเป็นเพียงการให้ความคิดแก่คุณ การใช้งานที่ถูกต้องจะขึ้นอยู่กับแอปพลิเคชันของคุณ
Oliver Moran

11
คุณอาจต้องการปกป้องตนเองจากคุณสมบัติต้นแบบ for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}
Romain Vergnory

3
@RomainVergnory เพื่อความปลอดภัยมากยิ่งขึ้นฉันเพียงคุณสมบัติการเริ่มต้นสร้างขึ้นในคอนสตรัคนี้แทน for (var prop in obj) {if (this.hasOwnProperty(prop)) {this[prop] = obj[prop];}}obj: สิ่งนี้ถือว่าคุณคาดหวังว่าเซิร์ฟเวอร์จะเติมคุณสมบัติทั้งหมด IMO ก็ควรโยนหาก obj.hasOwnProperty () ล้มเหลว ...
tekHedd

42

คุณต้องการที่จะเพิ่มฟังก์ชั่นการทำให้เป็นอันดับ / การเป็นซีเรียล 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

หมายเหตุ: หากคุณต้องการให้คุณสามารถเปลี่ยนคำจำกัดความของทรัพย์สินทั้งหมดเช่นthis.title, this.authorฯลฯ โดยvar title, var authorฯลฯ และเพิ่ม getters กับพวกเขาเพื่อให้บรรลุความหมาย UML


4
ฉันเห็นด้วย. การใช้งานนี้จะได้ผลอย่างแน่นอนและมันก็ยอดเยี่ยม ... เพียงแค่พูดเล็ก ๆ น้อย ๆ และเจาะจงไปที่ Book Object IMHO พลังของ JS มาจากต้นแบบและความสามารถในการมีคุณสมบัติพิเศษบางอย่างถ้าคุณต้องการ นั่นคือทั้งหมดที่ฉันพูด ฉันกำลังมองหาหนึ่งซับ: x .__ proto__ = X.prototype; (แม้ว่าจะไม่ใช่เบราว์เซอร์ IE ที่ใช้งานร่วมกันได้ในขณะนี้)
BMiner

4
อย่าลืมว่าtoJson()วิธีการของคุณ- ไม่ว่ามันจะมีคุณสมบัติของแต่ละคน hardcoded หรือใช้สำหรับแต่ละ - จะต้องเพิ่มรหัสหลบหลีก backslash สำหรับตัวละครบางตัวที่อาจจะอยู่ในแต่ละคุณสมบัติสตริง (ตัวอย่างเช่นชื่อหนังสืออาจมีเครื่องหมายคำพูด)
nnnnnn

1
ใช่ฉันรู้ว่าคำตอบของฉันเป็นตัวอย่างและเป็นคำตอบที่ดีที่สุดสำหรับคำถาม แต่ ... ไม่ใช่แม้กระทั่งจุดบวก ... ฉันไม่รู้ว่าทำไมฉันถึงเสียเวลากับการช่วยเหลือผู้อื่น
Gabriel Llamas

7
วันนี้ฉันจะใช้JSON.stringify()แทนการเขียนถึง JSon () ด้วยตัวเอง ไม่จำเป็นต้องคิดค้นใหม่ในขณะนี้ที่เบราว์เซอร์ที่ทันสมัยทั้งหมดรองรับ
ศิลา

2
เห็นด้วยกับ @skypecakes ถ้าคุณต้องการซีเรียลไลซ์ของชุดย่อยของคุณสมบัติเท่านั้นให้สร้างค่าคงที่ของคุณสมบัติที่สามารถทำให้เป็นอนุกรมได้ serializable = ['title', 'author', ...]. JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))
Atticus

19

โพสต์บล็อกที่ฉันพบว่ามีประโยชน์: การ ทำความเข้าใจต้นแบบ JavaScript

คุณสามารถยุ่งกับคุณสมบัติ __proto__ ของวัตถุ

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

สิ่งนี้ทำให้ fooJSON สามารถสืบทอดต้นแบบ Foo ได้

ฉันไม่คิดว่ามันจะทำงานได้ใน IE แต่อย่างน้อยจากสิ่งที่ฉันอ่าน


2
จริงๆแล้วบางอย่างเช่นนั้นเป็นสัญชาตญาณแรกของฉัน
Oliver Moran

14
โปรดทราบว่า__proto__มีมานานแล้วเลิก นอกจากนี้เพื่อเหตุผลด้านประสิทธิภาพเราไม่แนะนำให้แก้ไขคุณสมบัติภายใน [[Prototype]] ของวัตถุที่สร้างขึ้นแล้ว (โดยการตั้งค่า__proto__หรือด้วยวิธีอื่นใด)
Yu Asakusa

1
อนิจจาไม่มีวิธีแก้ปัญหาที่ไม่ได้เลิกใช้จริง ๆ แล้วมีความซับซ้อนมากกว่านี้ ...
Wim Leers

ฉันได้ทำการทดสอบประสิทธิภาพของการเปลี่ยนแปลง[[prototype]]แล้วและดูเหมือนว่าไม่เกี่ยวข้องกับ Chrome ในการเรียก Firefox ใหม่ช้ากว่าการใช้ต้นแบบและ Object.create เร็วที่สุด ฉันเดาว่าปัญหาของ FF คือการทดสอบครั้งแรกช้ากว่าครั้งล่าสุด ในโครเมียมทุกอย่างทำงานด้วยความเร็วเกือบเท่ากัน ฉันหมายถึงการเข้าถึงคุณสมบัติและการย้ายถิ่นของวิธีการ 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

ฉันไม่มีอะไรในคำถามหรือเพราะเหตุใดไม่มีใครพูดถึงreviverพารามิเตอร์ของJSON.parseตั้งแต่ปี 2011

นี่คือรหัสแบบง่าย ๆ สำหรับวิธีแก้ปัญหาที่ใช้งานได้: 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 Object นั้นไปใช้และเปลี่ยนเป็น JavaScript Object ที่เฉพาะเจาะจงได้ (เช่นกับต้นแบบบางตัว) ได้อย่างไร ขัดแย้งกับชื่อที่คุณถามเกี่ยวกับการแยกวิเคราะห์ 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

เพื่อความสมบูรณ์นี่คือหนึ่งซับที่เรียบง่ายฉันจบลงด้วย (ฉันไม่จำเป็นต้องตรวจสอบคุณสมบัติที่ไม่ใช่ฟู):

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 JSONรองรับการอ้างอิง (แบบวงกลม) และอินสแตนซ์ของคลาส

คุณต้องกำหนดวิธีการใหม่ 2 วิธีในชั้นเรียนของคุณ ( toDryบนต้นแบบและunDryวิธีคงที่) ลงทะเบียนชั้นเรียน ( Dry.registerClass) และปิดคุณไป


1

ในขณะที่นี่ไม่ใช่เทคนิคสิ่งที่คุณต้องการถ้าคุณรู้ก่อนว่าชนิดของวัตถุที่คุณต้องการจัดการคุณสามารถใช้วิธีการโทร / ใช้ของต้นแบบของวัตถุที่คุณรู้จัก

คุณสามารถเปลี่ยนสิ่งนี้

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 นั้นชัดเจนมาก แต่ถ้าคุณกำลังมองหาวิธีแก้ปัญหาในเชิงมุม js ฉันได้เขียนโมดูลที่ดีที่เรียกว่า Angular-jsClass ซึ่งทำได้อย่างง่ายดายการมีวัตถุที่กำหนดไว้ในสัญกรณ์ litaral นั้นไม่ดีเสมอเมื่อคุณมุ่งสู่โครงการใหญ่ แต่การบอกว่านักพัฒนาประสบปัญหาซึ่ง 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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.