【问题标题】:How to restore original object/type from JSON?如何从 JSON 恢复原始对象/类型?
【发布时间】:2012-12-25 02:17:30
【问题描述】:

使用 eval 或 JSON.parse 将 JSON 加载到 javascript 中的对象中很容易。

但是如果你有一个适当的“类”类函数,你如何将 JSON 数据导入其中?

例如

function Person(name) {
  this.name=name;
  this.address = new Array();
  this.friendList;

  this.promote = function(){
     // do some complex stuff
  }
  this.addAddress = function(address) {
    this.address.push(address)
  }
}

var aPersonJSON = '{\"name\":\"Bob\",\"address\":[{\"street\":\"good st\",\"postcode\":\"ADSF\"}]}'

var aPerson = eval( "(" + aPersonJSON + ")" ); // or JSON.parse
//alert (aPerson.name);    // Bob
var someAddress = {street:"bad st",postcode:"HELL"};
//alert (someAddress.street); // bad st
aPerson.addAddress(someAddress); // fail!

关键是我需要能够从 JSON 创建适当的 Person 实例,但我能得到的只是一个愚蠢的对象。我想知道它是否可以用原型做一些事情?

我不想解析 JSON 的每一行并将每个变量分配给对应的函数属性,这太难了。我实际拥有的 JSON 和函数比上面的例子复杂得多。

我假设可以将函数方法 JSONify 到 JSON 字符串中,但是由于我需要使结果数据尽可能小,这不是一种选择 - 我只想存储和加载数据,而不是 javascript 代码对于方法。

如果我可以帮助它(但可能是唯一的方法),我也不想将 JSON 加载的数据作为子对象,例如

function Person(name) {
  this.data = {};
  this.data.name=name;
}

var newPerson = new Person("");
newPerson.data = eval( "(" + aPersonJSON + ")" );
alert (newPerson.data.name); // Bob

有什么想法吗?

【问题讨论】:

  • 请使用JSON.parse 而不是eval 来解析JSON。如果目标浏览器不支持 JSON.parse(即 IE7 或更早版本),则可以通过 Crockford 的 json.js(在内部使用 eval,但也提供完整性验证)或 json2.js 的 shim 随时可用。
  • 嗨,问题不是 eval 或 parse,它们都产生相同的无类型对象,我正在尝试找到一种方法将 JSON 放入 Person 函数中。对此有何想法?
  • @JohnLittle 从不使用 eval!,它是许多安全问题的根源(代码注入等)read this
  • @thepoosh 这就是为什么它是一个评论 :) 在任何情况下,JSON 都不是“与特定类型的对象相关联”。两种方法是添加单例方法或使用适当的构造函数/[[prototype]] 将 JSON 中的值(可能是深度复制)复制到新对象。
  • 你不能只迭代对象并替换 .prototype 和构造函数吗?

标签: javascript json


【解决方案1】:

你需要使用reviver函数:

// Registry of types
var Types = {};

function MyClass(foo, bar) {
  this._foo = foo;
  this._bar = bar;
}
Types.MyClass = MyClass;

MyClass.prototype.getFoo = function() {
  return this._foo;
}

// Method which will provide a JSON.stringifiable object
MyClass.prototype.toJSON = function() {
  return {
    __type: 'MyClass',
    foo: this._foo,
    bar: this._bar
  };
};

// Method that can deserialize JSON into an instance
MyClass.revive = function(data) {
  // TODO: do basic validation
  return new MyClass(data.foo, data.bar);
};

var instance = new MyClass('blah', 'blah');

// JSON obtained by stringifying an instance
var json = JSON.stringify(instance); // "{"__type":"MyClass","foo":"blah","bar":"blah"}";

var obj = JSON.parse(json, function(key, value) {
  return key === '' && value.hasOwnProperty('__type')
    ? Types[value.__type].revive(value)
    : this[key];
});

obj.getFoo(); // blah

真的没有别的办法……

【讨论】:

  • 有没有约定调用这个函数reviver
  • 在 JSON 对象的规范中,方法(和形式参数)被称为 reviver。
【解决方案2】:

许多框架都提供了“扩展”功能,可以将字段从一个对象复制到另一个对象。你可以把它和 JSON.parse 结合起来做你想做的事。

newPerson = new Person();
_.extend(newPerson, JSON.parse(aPersonJSON));

如果您不想包含下划线之类的内容,您可以随时复制扩展函数或编写自己的函数。

Coffeescript 示例,因为我很无聊:

JSONExtend = (obj, json) ->
  obj[field] = value for own field, value of JSON.parse json
  return obj

class Person
  toString: -> "Hi I'm #{@name} and I'm #{@age} years old."


dude = JSONExtend new Person, '{"name":"bob", "age":27}'
console.log dude.toString()

【讨论】:

  • 冒着招致各种愤怒的风险,为 Coffeescript +1!
【解决方案3】:

聚会有点晚了,但这可能会对某人有所帮助。 这就是我解决它的方法,ES6 语法:

class Page 
{
   constructor() {
      this.__className = "Page";
   }

   __initialize() {
       // Do whatever initialization you need here.
       // We'll use this as a makeshift constructor.
       // This method is NOT required, though
   }
}

class PageSection
{
   constructor() {
      this.__className = "PageSection";
   }
}

class ObjectRebuilder
{
    // We need this so we can instantiate objects from class name strings
    static classList() {
        return {
            Page: Page,
            PageSection: PageSection
        }
    }

    // Checks if passed variable is object.
    // Returns true for arrays as well, as intended
    static isObject(varOrObj) {
        return varOrObj !== null && typeof varOrObj === 'object';
    }

    static restoreObject(obj) {
        let newObj = obj;

        // At this point we have regular javascript object
        // which we got from JSON.parse. First, check if it
        // has "__className" property which we defined in the
        // constructor of each class
        if (obj.hasOwnProperty("__className")) {
            let list = ObjectRebuilder.classList();

            // Instantiate object of the correct class
            newObj = new (list[obj["__className"]]);

            // Copy all of current object's properties
            // to the newly instantiated object
            newObj = Object.assign(newObj, obj);

            // Run the makeshift constructor, if the
            // new object has one
            if (newObj.__initialize === 'function') {
                newObj.__initialize();
            }
        }

        // Iterate over all of the properties of the new
        // object, and if some of them are objects (or arrays!) 
        // constructed by JSON.parse, run them through ObjectRebuilder
        for (let prop of Object.keys(newObj)) {
            if (ObjectRebuilder.isObject(newObj[prop])) {
                newObj[prop] = ObjectRebuilder.restoreObject(newObj[prop]);
            }
        }

        return newObj;
    }
}

let page = new Page();
let section1 = new PageSection();
let section2 = new PageSection();

page.pageSections = [section1, section2];

let jsonString = JSON.stringify(page);
let restoredPageWithPageSections = ObjectRebuilder.restoreObject(JSON.parse(jsonString));

console.log(restoredPageWithPageSections);

您的页面应恢复为Page 类的对象,数组包含PageSection 类的2 个对象。无论深度如何,递归都会一直工作到最后一个对象。

@Sean Kinsey 的回答帮助我找到了解决方案。

【讨论】:

    【解决方案4】:

    最简单的方法是使用JSON.parse 解析您的字符串,然后将对象传递给函数。 JSON.parse 是在线 json2 库的一部分。

    【讨论】:

    • JSON 方法在现代浏览器中可用...使用库作为旧浏览器的后备
    • JSON.stringify() 函数不是用于解析;它用于将对象变成 JSON 字符串。
    • 你好,这是关键,如何将生成的复杂对象转化为函数?你不能只传递它——除非我写了一些非常复杂的东西来遍历对象,将每个子对象和数据项复制到我不想做的 Person 对象中,尤其是当它经常更改时。
    【解决方案5】:

    我对此并不太感兴趣,但是 aPerson.addAddress 应该不起作用, 为什么不直接赋值给对象呢?

    aPerson.address.push(someAddress);
    alert(aPerson.address); // alert [object object]
    

    【讨论】:

      【解决方案6】:

      以防万一有人需要,这里有一个纯 javascript 扩展函数 (这显然属于对象定义)。

        this.extend = function (jsonString){
          var obj = JSON.parse(jsonString)
          for (var key in obj) {
              this[key] = obj[key]
              console.log("Set ", key ," to ", obj[key])
              }   
          } 
      

      请不要忘记删除console.log:P

      【讨论】:

        【解决方案7】:

        TL; DR: 这是我使用的方法:

        var myObj = JSON.parse(raw_obj_vals);
        myObj = Object.assign(new MyClass(), myObj);
        

        详细示例:

        const data_in = '{ "d1":{"val":3,"val2":34}, "d2":{"val":-1,"val2":42, "new_val":"wut?" } }';
        class Src {
            val1 = 1;
            constructor(val) { this.val = val; this.val2 = 2; };
            val_is_good() { return this.val <= this.val2; }
            get pos_val() { return this.val > 0; };
            clear(confirm) { if (!confirm) { return; }; this.val = 0; this.val1 = 0; this.val2 = 0; };
        };
        const src1 = new Src(2); // standard way of creating new objects
        var srcs = JSON.parse(data_in);
        // ===================================================================
        // restoring class-specific stuff for each instance of given raw data
        Object.keys(srcs).forEach((k) => { srcs[k] = Object.assign(new Src(), srcs[k]); });
        // ===================================================================
        
        console.log('src1:', src1);
        console.log("src1.val_is_good:", src1.val_is_good());
        console.log("src1.pos_val:", src1.pos_val);
        
        console.log('srcs:', srcs)
        console.log("srcs.d1:", srcs.d1);
        console.log("srcs.d1.val_is_good:", srcs.d1.val_is_good());
        console.log("srcs.d2.pos_val:", srcs.d2.pos_val);
        
        srcs.d1.clear();
        srcs.d2.clear(true);
        srcs.d3 = src1;
        const data_out = JSON.stringify(srcs, null, '\t'); // only pure data, nothing extra. 
        console.log("data_out:", data_out);
        • 简单高效。合规(2021 年)。没有依赖关系。
        • 适用于不完整的输入,保留默认值而不是缺失字段(升级后特别有用)。
        • 适用于过多输入,保留未使用的数据(保存时不会丢失数据)。
        • 可以很容易地扩展到更复杂的情况,具有多个嵌套类以及类类型提取等。
        • 无论必须分配多少数据或嵌套多深(只要从简单对象恢复,请参阅Object.assign() limitations

        【讨论】:

          【解决方案8】:

          现代方法(2021 年 12 月)是使用 @badcafe/jsonizer : https://badcafe.github.io/jsonizer

          • 与其他解决方案不同,它不会通过注入的类名污染您的数据,
          • 它重新定义了预期的数据层次结构。
          • 以下是 Typescript 中的一些示例,但在 JS 中也同样有效

          在展示一个类的例子之前,让我们从一个简单的数据结构开始:

          const person = {
              name: 'Bob',
              birthDate: new Date('1998-10-21'),
              hobbies: [
                  {   hobby: 'programming',
                      startDate: new Date('2021-01-01'),
                  },
                  {   hobby: 'cooking',
                      startDate: new Date('2020-12-31'),
                  },
              ]
          }
          const personJson = JSON.stringify(person);
          // store or send the data
          

          现在,让我们使用 Jsonizer ?

          // in Jsonizer, a reviver is made of field mappers :
          const personReviver = Jsonizer.reviver<typeof person>({
              birthDate: Date,
              hobbies: {
                  '*': {
                      startDate: Date
                  }
              }
          });
          const personFromJson = JSON.parse(personJson, personReviver);
          

          JSON 文本中的每个日期字符串都已映射到解析结果中的 Date 对象。

          Jsonizer 可以用递归嵌套的自定义类、第三方类、内置类或子 JSON 结构(数组、对象)无差别地恢复 JSON 数据结构(数组、对象)或类实例。

          现在,让我们改用一个类:

          // in Jsonizer, a class reviver is made of field mappers + an instance builder :
          @Reviver<Person>({ // ?  bind the reviver to the class
              '.': ({name, birthDate, hobbies}) => new Person(name, birthDate, hobbies), // ?  instance builder
              birthDate: Date,
              hobbies: {
                  '*': {
                      startDate: Date
                  }
              }
          })
          class Person {
              constructor( // all fields are passed as arguments to the constructor
                  public name: string,
                  public birthDate: Date
                  public hobbies: Hobby[]
              ) {}
          }
          interface Hobby {
              hobby: string,
              startDate: Date
          }
          
          const person = new Person(
              'Bob',
              new Date('1998-10-21'),
              [
                  {   hobby: 'programming',
                      startDate: new Date('2021-01-01'),
                  },
                  {   hobby: 'cooking',
                      startDate: new Date('2020-12-31'),
                  },
              ]
          );
          const personJson = JSON.stringify(person);
          
          const personReviver = Reviver.get(Person); // ?  extract the reviver from the class
          const personFromJson = JSON.parse(personJson, personReviver);
          

          最后,让我们使用 2 个类:

          @Reviver<Hobby>({
              '.': ({hobby, startDate}) => new Hobby(hobby, startDate), // ?  instance builder
              startDate: Date
          })
          class Hobby {
              constructor (
                  public hobby: string,
                  public startDate: Date
              ) {}
          }
          
          @Reviver<Person>({
              '.': ({name, birthDate, hobbies}) => new Person(name, birthDate, hobbies), // ?  instance builder
              birthDate: Date,
              hobbies: {
                  '*': Hobby  // ?  we can refer a class decorated with @Reviver
              }
          })
          class Person {
              constructor(
                  public name: string,
                  public birthDate: Date,
                  public hobbies: Hobby[]
              ) {}
          }
          
          const person = new Person(
              'Bob',
              new Date('1998-10-21'),
              [
                  new Hobby('programming', new Date('2021-01-01')),
                  new Hobby('cooking', new Date('2020-12-31')
              ]
          );
          const personJson = JSON.stringify(person);
          
          const personReviver = Reviver.get(Person); // ?  extract the reviver from the class
          const personFromJson = JSON.parse(personJson, personReviver);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-07-10
            • 2016-03-30
            • 2015-12-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多