TypeScript 实现 JSON字符串 和 实例化Class对象 之间的相互转换

拥有这个功能,我们可以轻松地将 已实例化类 转为 JSON 保存起来(例如使用 localStorage),在需要时(例如页面关闭后再重新打开)轻松地将 JSON 重新转为 实例化类,实现数据的持久化。在实际运用中,就可以轻松调用 Class 中的方法,对数据进行处理操作。JSON 是普通的数据,无直接处理能力,而一个 类 具有业务处理能力,实现题目的功能就好比是在两者间搭建桥梁。

前言

首先,以 Java 的 Gson 库为例,Gson 提供了 fromJson()toJson() 两个方法,这正是我们想要的。

在 TypeScript 中,其实已有类似的依赖:

class-transformer | What is class-transformer

But…

如果你想自己动手,实现一个类似于 Gson 和 class-transformer 的功能。嗯,那就来搞接下来的折腾把…

再说一遍:class-transformer 很香

下面的方法在我写的 duty-schedule-core 这个项目中,有较好的运用。如有好的建议或错漏之处,敬请提出或指正

注:为了语言更为简短,文中所有 “JSON” 指的是 “JSON字符串”,”实例化类” 指的是 “实例化Class对象”(包含题目所述的意义)

核心:Model 类

若一个类中有一个字段,使用 字段修饰符 就可以得到 字段名

我们将这些 调用了 字段修饰符字段名 存储起来,就可以区分出哪些字段将会被存储

将这个 字段修饰符 命名为 StoreField

fromJson() 实现的原理很简单就是:遍历 通过 字段修饰符 记录得到的存储字段名,依次判断字段的值类型,来执行对应的 导入操作;导出为 JSON 反过来同理。

下面直接贴代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import _ from 'lodash'

/** 所有 Stored 字段的参数 */
const AllStoredFieldOpts: { [modelName: string]: { [fieldName: string]: FieldOpts } } = {}

class Model {
/** Stored 字段的参数 */
public get StoredFieldOpts () {
return AllStoredFieldOpts[this.constructor.name] || {}
}

/** Stored 字段 名称 */
public get StoredFieldNames () {
return Object.keys(AllStoredFieldOpts[this.constructor.name] || {})
}

/** 导出 Stored 字段数据为 Obj */
public toObj () {
const result: any = {}
_.forEach(this.StoredFieldOpts, ({ useModel: UsedModel }, f) => {
const value = (this as any)[f]
if (UsedModel) { // 使用 Model
if (Array.isArray(value)) {
const arr: any[] = result[f] = []
_.forEach(value, (item: Model) => { arr.push(item.toObj()) })
} else if (typeof value === 'object' && value !== null && !(value instanceof Model)) {
const obj: any = result[f] = {}
_.forEach(value, (item: any, key: any) => { obj[key] = (item as Model).toObj() })
} else {
result[f] = value ? (value as Model).toObj() : value
}
} else {
result[f] = value
}
})
return result
}

/** 从对象中导入 Stored 字段数据 */
public fromObj (sourceObj: any) {
_.forEach(this.StoredFieldOpts, ({ useModel: UsedModel }, f) => {
if (!_.has(sourceObj, f) || sourceObj[f] === undefined) return // 注意:数据不会全部清除,和 Object.assign 类似
const value = sourceObj[f]
if (UsedModel) { // 使用 Model
if (Array.isArray(value)) {
const arr: any[] = (this as any)[f] = []
_.forEach(value, (item) => { arr.push((new UsedModel() as Model).fromObj(item)) })
} else if (typeof value === 'object' && value !== null && !((this as any)[f] instanceof Model)) {
// 注:字段满足此判断的前提是:必须要有默认值 = {}
const obj: any = (this as any)[f] = {}
_.forEach(value, (item, key) => { obj[key] = (new UsedModel() as Model).fromObj(item) })
} else {
(this as any)[f] = (new UsedModel() as Model).fromObj(value)
}
} else {
(this as any)[f] = value
}
})
return this
}

/** 导出 Stored 字段数据为 JSON */
public toJSON () {
return JSON.stringify(this.toObj())
}

/** 从 JSON 导入 Stored 字段数据 */
public fromJSON (aJSON: string) {
this.fromObj(JSON.parse(aJSON))
return this
}
}

/** Stored 字段参数 */
type FieldOpts = {
/** 使用 Model */
useModel?: typeof Model
}

/** 标记为 Stored 字段 */
export const StoreField = (opts: FieldOpts = {}) => ({ constructor: { name: modelName } }: object, fieldName: string) => {
const modelFields = AllStoredFieldOpts[modelName] || (AllStoredFieldOpts[modelName] = {})
if (!_.has(modelFields, fieldName)) modelFields[fieldName] = opts
}

为了方便,所以引用了 lodash 依赖

注:以下所有继承了 Model 的类都称之为 Model,所以下面定义的 One, Test1, Test2 都称为 Model

Model 字段类型标记

Model中 存储字段

@StoreField() 这个 字段修饰符 标记字段为 存储 的字段

当调用 toJSON() 时,这些字段就会显示出来

Model中 包含另一个Model(层级嵌套)

一个 Model 中可以无限级地包含另外的 Model

标记方法:

1
2
3
4
5
6
class X extends Model {
@StoreField({
useModel: 某个Model的Type
})
test!: 某个Model的Type
}

如下文,定义的 class One 包含 class Test1

Model 的定义

One 类 字段:

  • name,存储,无默认值,字符串
  • name2,不存储,默认值 ‘No name’,字符串
  • name3,存储,默认值 ‘???’,字符串
  • test1List,包含另一个 Model 名为 Test1,存储,默认值为空数组,类型为 Array
  • test2,包含另一个 Model 名为 Test2,存储,无默认值,类型为 Test2

Test1 类:字段 test,存储,无默认值,字符串

Test2 类:字段 hhh,存储,默认值 “23333”,字符串

这三个类,都继承 Model 类,Model 类是核心(包含方法 toJSONfromJSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import Model, { StoreField } from './_Model'

class One extends Model {
@StoreField()
name!: string

name2: string = 'No name'

@StoreField()
name3: string = '???'

@StoreField({
useModel: Test1
})
test1List: Test1[] = []

@StoreField({
useModel: Test2
})
test2!: Test2

get firstName () {
return this.getFirstName()
}

getFirstName () {
return this.name.split(' ')[1] || null
}
}

class Test1 extends Model {
@StoreField() test!: string
}

class Test2 extends Model {
@StoreField() hhh: string = "23333"
}

Model 中的方法

  • toJSON() 将 实例化类 转为 JSON
  • fromJSON() 将 JSON 转为 实例化类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const one = new One()
one.name2 // "No name"
one.name3 // "???"

one.name = 'Kirigaya Kazuto'
one.name2 = 'Kirito'

const test1 = new Test1()
test1.test = '233'
one.test1List.push(test1)

one.test2 = new Test2()

one.toJSON() // `{"name":"Kirigaya Kazuto", "name3": "???", "test1List": [{"test": "233"}], "test2": {"hhh": "23333"}}`
1
2
3
4
5
6
const one = new One().fromJSON(`{"name":"Kirigaya Kazuto", "test1List":[{"test":"233"}]}`) // 注:没有 name3,则为 class 中设定的默认值

one.toJSON() // `{"name":"Kirigaya Kazuto", "name3": "???", "test1List":[{"test":"233"}]}` 注:name3 默认值为 ???

one.firstName // "Kazuto"
one.getFirstName() // "Kazuto"
1
2
3
4
const rawJson = `{"name":"Kirigaya Kazuto", "name3": "Kirito", "test1List":[{"test":"233"}]}`
const one2 = new One().fromJSON(rawJson)

one2.toJSON() === rawJson // true
本站文章除注明转载外均为原创,未经允许不要转载哇. ヾ(゚ー゚ヾ) https://qwqaq.com/52433ab.html
分享到