【问题标题】:Parse JSON String into a Particular Object Prototype in JavaScript在 JavaScript 中将 JSON 字符串解析为特定的对象原型
【发布时间】:2011-08-17 22:24:50
【问题描述】:

我知道如何解析 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 浏览器中不可用。

【问题讨论】:

标签: javascript json parsing object prototype


【解决方案1】:

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

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

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

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

【讨论】:

  • Object.assign 在旧版浏览器(包括 IE 和旧版 Android 浏览器)中不可用。 kangax.github.io/compat-table/es6/…
  • 还有一个很大的警告不要使用Object.setPrototypeOf(...)developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • @SimonEpskamp 该代码不起作用。检查你的 url,setPrototypeOf 的第二个参数是属性描述符。
  • 如果有一些属性也需要原型,设置原型的解决方案不起作用。换句话说:它只解决了第一级数据层次结构。
  • 在下面查看我的解决方案,它递归地应用 Object.assign(..) 可以自动解析属性(提前提供一些信息)
【解决方案2】:

请参见下面的示例(此示例使用本机 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 对象并将属性从 fooJSON 复制到新的 Foo 对象中。最后,设置 fooJSON 指向 Foo 对象。
  • 这是非常危险的。如果 obj 具有不在 Foo 定义中的属性,您将创建一个 Foo 对象,该对象具有一个您不知道其名称的额外隐藏属性...而不是循环,我将简单地做:this.a = obj。 a 和 this.b = obj.b。或者直接将“a”和“b”作为参数传递:new Foo (obj.a, obj.b)
  • GagleKas 的建议值得一听。 (虽然“非常危险”有点OTT。)上面的例子只是为了给你一个想法。正确的实现取决于您的应用程序。
  • 您可能希望保护自己免受原型属性的影响。 for (var prop in obj) {if (obj.hasOwnProperty(prop)) {this[prop] = obj[prop];}}
  • @RomainVergnory 为了更加安全,我只初始化在构造函数中创建的属性,而不是 obj:for (var prop in obj) {if (this.hasOwnProperty(prop)) {this[prop] = obj[prop];}}。这假设您希望服务器填充所有属性,如果 obj.hasOwnProperty() 失败,IMO 也应该抛出......
【解决方案3】:

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

你想实现这个:

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

【讨论】:

  • 我同意。这个实现肯定会奏效,而且很棒......只是有点罗嗦并且特定于 Book Object。恕我直言,JS 的力量来自原型和如果你想拥有一些额外属性的能力。这就是我要说的。我真的在寻找单线: x.__proto__ = X.prototype; (虽然目前不兼容IE浏览器)
  • 不要忘记您的 toJson() 方法 - 无论它是否具有硬编码的单个属性或使用每个属性 - 都需要为每个字符串属性中的某些字符添加反斜杠转义码. (例如,书名可能有引号。)
  • 是的,我知道,我的回答是一个例子,也是该问题的最佳答案,但是......甚至不是一个积极的观点......我不知道我为什么要浪费时间帮助别人
  • 这些天我会使用 JSON.stringify() 而不是自己写 toJSon()。现在所有现代浏览器都支持它,无需重新发明轮子。
  • 同意@skypecakes。如果您只想序列化属性的子集,请创建一个可序列化属性的常量。 serializable = ['title', 'author', ...]JSON.stringify(serializable.reduce((obj, prop) => {...obj, [prop]: this[prop]}, {}))
【解决方案4】:

我发现有用的一篇博文: Understanding JavaScript Prototypes

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

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

这允许 fooJSON 继承 Foo 原型。

不过,我认为这在 IE 中不起作用……至少从我所读到的内容来看。

【讨论】:

  • 其实,这样的事情是我的第一直觉。
  • 请注意,__proto__ 长期以来一直是deprecated。此外,出于性能原因,不建议修改已创建对象的 [[Prototype]] 内部属性(通过设置__proto__ 或任何其他方式)。
  • 唉,没有一个实际不被弃用的解决方案比这复杂得多……
  • 我已经对更改 [[prototype]] 的性能进行了一些测试,这在 Chrome 中似乎无关紧要。在 Firefox 中调用 new 比使用原型要慢,而 Object.create 最快。我想 FF 的问题是第一次测试比最后一次慢,只是执行顺序很重要。在 chrome 中,一切都以几乎相同的速度运行。我的意思是属性访问和方法调用。使用新的肌酸更快,但这并不重要。请参阅:jsperf.com/prototype-change-test-8874874/1 和:jsperf.com/prototype-changed-method-call
  • 我想这些天,人们会打电话给Object.setPrototypeOf(fooJSON, Foo.prototype),而不是设置fooJSON.__proto__...对吗?
【解决方案5】:

我是否遗漏了问题中的某些内容,或者为什么自 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:你的问题令人困惑:>>很好,但是我怎样才能把那个 JavaScript 对象变成一个特定的 JavaScript 对象(即具有特定原型)? 与标题相矛盾,您在其中询问 JSON 解析,但引用的段落询问 JS 运行时对象原型替换。

【讨论】:

    【解决方案6】:

    当前接受的答案对我不起作用。您需要正确使用 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
    
    

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

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

          【讨论】:

            【解决方案10】:

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

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

            【讨论】:

              【解决方案11】:

              虽然这在技术上不是您想要的,但如果您事先知道要处理的对象类型,则可以使用已知对象原型的调用/应用方法。

              你可以改变这个

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

              到这里

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

              【讨论】:

                【解决方案12】:

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

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

                【讨论】:

                  【解决方案13】:

                  这是一个使用打字稿和装饰器的解决方案。

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

                  如何使用:

                  @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 并递归遍历它(从每个序列化属性的最后一步开始)。将结果分配给相应的属性。

                  • 使用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 新手(

                  它可能更干净:不幸的是我没有找到摆脱@SerializableProp的冗余参数的方法。

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

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

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

                  【讨论】:

                    【解决方案14】:

                    一个很简单的获得想要的效果的方法是在生成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"
                    

                    【讨论】:

                      【解决方案15】:

                      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

                      【讨论】:

                        猜你喜欢
                        • 2013-05-12
                        • 1970-01-01
                        • 2013-03-12
                        • 1970-01-01
                        • 1970-01-01
                        • 2019-07-12
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多