【问题标题】:JSON to TypeScript class instance? [duplicate]JSON 到 TypeScript 类实例? [复制]
【发布时间】:2015-06-27 19:41:49
【问题描述】:

我已经做了很多研究,但我对我的发现并不完全满意。只是为了确定这是我的问题: 将 JSON 反序列化为 TypeScript 运行时类实例的最强大、最优雅的自动化解决方案实际上是什么?

假设我上了这门课:

class Foo {
  name: string;
  GetName(): string { return this.name };
}

假设我得到了这个用于反序列化的 JSON 字符串:

{"name": "John Doe"}

获取名称设置为“John Doe”的 Foo 类实例和 GetName() 方法工作的最佳和最可维护的解决方案是什么?我问得非常具体,因为我知道反序列化为纯数据对象很容易。我想知道是否可以使用工作方法获取类实例,而无需进行任何手动解析或任何手动数据复制。如果无法实现完全自动化的解决方案,那么下一个最佳解决方案是什么?

【问题讨论】:

  • 既然name 字段是公开的,那么GetName 方法是干什么用的?
  • 我猜你可以认为它是一个篡改的例子;)关键是要得到一个答案,它产生一个包含所有方法的类的真实实例,而不仅仅是一个反序列化的类实例,它只包含数据,并且不允许调用声明的方法。

标签: json typescript deserialization


【解决方案1】:

这个问题比较宽泛,所以我将给出几个解决方案。

解决方案 1:辅助方法

这是一个使用帮助器方法的示例,您可以根据自己的需要进行更改:

class SerializationHelper {
    static toInstance<T>(obj: T, json: string) : T {
        var jsonObj = JSON.parse(json);

        if (typeof obj["fromJSON"] === "function") {
            obj["fromJSON"](jsonObj);
        }
        else {
            for (var propName in jsonObj) {
                obj[propName] = jsonObj[propName]
            }
        }

        return obj;
    }
}

然后使用它:

var json = '{"name": "John Doe"}',
    foo = SerializationHelper.toInstance(new Foo(), json);

foo.GetName() === "John Doe";

高级反序列化

这还可以通过将您自己的 fromJSON 方法添加到类中来实现一些自定义反序列化(这与 JSON.stringify 已经使用 toJSON 方法的方式很好,如下所示):

interface IFooSerialized {
    nameSomethingElse: string;
}

class Foo {
  name: string;
  GetName(): string { return this.name }

  toJSON(): IFooSerialized {
      return {
          nameSomethingElse: this.name
      };
  }

  fromJSON(obj: IFooSerialized) {
        this.name = obj.nameSomethingElse;
  }
}

然后使用它:

var foo1 = new Foo();
foo1.name = "John Doe";

var json = JSON.stringify(foo1);

json === '{"nameSomethingElse":"John Doe"}';

var foo2 = SerializationHelper.toInstance(new Foo(), json);

foo2.GetName() === "John Doe";

解决方案 2:基类

另一种方法是创建自己的基类:

class Serializable {
    fillFromJSON(json: string) {
        var jsonObj = JSON.parse(json);
        for (var propName in jsonObj) {
            this[propName] = jsonObj[propName]
        }
    }
}

class Foo extends Serializable {
    name: string;
    GetName(): string { return this.name }
}

然后使用它:

var foo = new Foo();
foo.fillFromJSON(json);

使用基类实现自定义反序列化的方法太多了,所以我将根据您的需要来决定。

【讨论】:

  • 谢谢。这两种解决方案似乎都比我之前找到的所有解决方案都要好得多。稍后再试一试。
  • @Klaus 的一个问题是如果您有嵌套对象。例如,如果Foo 有一个类型为Bar 的属性。这个问题有点复杂……
  • 该死的。嵌套对象实际上是我稍后要问的另一个问题。我觉得我对 TypeScript 的了解还不够,无法正确地提出后续问题。我不介意您是否发布一些关键字,尽管这可以让我自己寻找嵌套问题的解决方案。再次感谢您在上面的深入回答。
  • 要支持嵌套,能不能像让fillFromJson方法递归一样简单?即,您的空检查可以确定当前的 json 参数是否是一个对象 - 如果是,则递归继续:this[propName] = typeof jsonObj[propName] == 'object' ? fillFromJson(jsonObj[propName]) : jsonObj[propName]。你试过这个吗?我可以尝试看看它是怎么回事,我只是想知道你是否知道任何可以节省我时间的陷阱。谢谢!
  • @DannyBullis 我想回来看看这个问题,以便开发出更好的解决方案。不过在未来的某个时候。在此之前,这是使用recursion 的另一种方法。基本上,您还需要知道属性的类型。
【解决方案2】:

您现在可以使用Object.assign(target, ...sources)。按照您的示例,您可以像这样使用它:

class Foo {
  name: string;
  getName(): string { return this.name };
}

let fooJson: string = '{"name": "John Doe"}';
let foo: Foo = Object.assign(new Foo(), JSON.parse(fooJson));

console.log(foo.getName()); //returns John Doe

Object.assignECMAScript 2015 的一部分,目前可在大多数现代浏览器中使用。

【讨论】:

  • 如果Foo缺少默认构造函数,可以通过Object.create(Foo.prototype)创建目标实例,完全绕过构造函数。
  • 不适用于嵌套对象
  • @jzqa 你能举一个失败的例子吗?我看到它的复制很好。
  • 在使用普通的JSON 解析时,嵌套对象不会映射到它们的强类型。我建议使用像 class-transformer 这样的包来正确映射任何嵌套对象的对象及其类型。他们的文档相对容易理解并且也很容易上手
【解决方案3】:

将 JSON 反序列化为 TypeScript 运行时类实例的最强大、最优雅的自动化解决方案实际上是什么?

使用property decoratorsReflectDecorators 来记录可在反序列化过程中使用的运行时可访问的类型信息,提供了一种令人惊讶的干净且适应性广泛的方法,该方法也非常适合现有代码。它也是完全自动化的,也适用于嵌套对象。

这个想法的一个实现是TypedJSON,我正是为此任务创建的:

@JsonObject
class Foo {
    @JsonMember
    name: string;

    getName(): string { return this.name };
}
var foo = TypedJSON.parse('{"name": "John Doe"}', Foo);

foo instanceof Foo; // true
foo.getName(); // "John Doe"

【讨论】:

  • 如果您希望更多人使用您的库,我建议您在 npm 上发布并修复这些类型......使用指向您的 index.d.ts 文件的 package.json
  • 这个库可以与 React 一起使用吗?
  • @Keselme 是的,我自己在各种使用 React 实现视图层的项目中使用它。
  • @JohnWeisz,我在使用 React 时遇到问题,我收到一条错误消息,上面写着 could not resolve detected property constructor at runtime. 我实际上打开了一个问题,你能看一下吗? github.com/JohnWeisz/TypedJSON/issues/130
【解决方案4】:

为什么你不能做这样的事情?

class Foo {
  constructor(myObj){
     Object.assign(this, myObj);
  }
  get name() { return this._name; }
  set name(v) { this._name = v; }
}

let foo = new Foo({ name: "bat" });
foo.toJSON() //=> your json ...

【讨论】:

  • 很遗憾,这不适用于嵌套对象。
【解决方案5】:

我在处理 Typescript 类和 json 对象时发现的最佳解决方案:在您的 Typescript 类中添加一个以 json 数据作为参数的构造函数。在该构造函数中,您使用 jQuery 扩展您的 json 对象,如下所示:$.extend(this, jsonData)。 $.extend 允许在添加 json 对象的属性时保留 javascript 原型。

export class Foo
{
    Name: string;
    getName(): string { return this.Name };

    constructor( jsonFoo: any )
    {
        $.extend( this, jsonFoo);
    }
}

在您的 ajax 回调中,将您的 json 转换为您的 typescript 对象,如下所示:

onNewFoo( jsonFoos : any[] )
{
  let receviedFoos = $.map( jsonFoos, (json) => { return new Foo( json ); } );

  // then call a method:
  let firstFooName = receviedFoos[0].GetName();
}

如果您不添加构造函数,只需在您的 ajax 回调中调用:

let newFoo = new Foo();
$.extend( newFoo, jsonData);
let name = newFoo.GetName()

...但是如果您也想转换子 json 对象,构造函数将很有用。看我的详细回答here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-18
    • 2019-06-10
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    • 2017-04-28
    • 2021-02-19
    相关资源
    最近更新 更多