实体数据框架
npm install entity-framework
> Enables experimental support for decorators, which is in stage 2 of the TC39 standardization process.
see more about experimentalDecorators
一个类声明在继承 DataModel 后可视为一个模型声明, 该类声明的实例可以进行序列化与反序列化操作.
``typescript
class ResponseData
@Mapping()
public data?: T
@Mapping()
public msg?: string
@Mapping()
public code?: number
public others?: unknown
}
`
`typescript
const res = new ResponseData
res.code = 0
res.data = true
res.msg = 'success'
res.others = 'anything'
const data: model.Data = res.serialize()
expect(data.others).toBeUndefined()
expect(data).toEqual({ data: true, msg: 'success', code: 0 })
`
`typescript
const res = new ResponseData
const data: model.Data = { data: true, msg: 'success', code: 0 }
res.deserialize(data)
expect(res.code).toEqual(data.code)
expect(res.msg).toEqual(data.msg)
expect(res.data).toEqual(data.data)
expect(res.others).toBeUndefined()
`
更多示例请参考 测试用例
- test/serialize.spec.tstest/deserialize.spec.ts
-
- 映射数据 Data
原生 Object 对象数据, 可通过反序列化操作填充到模型实例中.
- 模型 DataModel
继承自 DataModel 的类型声明, 拥有 serialize 与 deserialize 实例方法.
- 注解 Annotation
用于描述模型与映射数据的关联关系.
- 元数据 Metadata
用于描述模型的数据.
- 操作命令 OperationCommand
命令分为序列化命令与反序列化命令, 在执行序列化操作时, 所有的序列化命令按优先级(priority)升序执行; 执行反序列化操作时, 所有的反序列化命令按优先级(priority)升序执行.
- 数据访问路径 DataAccessorPath
描述数据在映射数据结构中所在位置的拥有特定语法的的字符串.
`json`
{
"id": 1,
"name": "Xin Hua book store",
"filters": [
{
"name": "5天精通数据格式转换"
},
"JavaScript 犀牛书",
"编译原理 鲸书",
"OOP 设计模式",
"反射",
"装饰器",
"Jest TDD 测试驱动"
],
"stores": [
{
"id": 2,
"name": "Xin Hua book store 2"
},
{
"id": 3,
"name": "Xin Hua book store 3"
},
{
"id": 4,
"name": "Xin Hua book store 4"
}
]
}
| 数据访问路径 | 映射数据 | 说明 |
| -- | -- | -- |
| @Mapping({ path: 'id' }) | 1 | 如果类型成员字段与path一致, 可缺省 |
| @Mapping({ path: 'filters[0]' }) | { "name": "5天精通数据格式转换" } | 通过索引范文映射数据数组的特定位置 |
| @Mapping({ path: 'filters[1:]' }) | ["JavaScript...", "编译...", ...] | filters数组的后6项数据 |
| @Mapping({ path: 'stores' }) | [{"id": 2, "name": "..."}, ...] | stores数组的全部数据 |
- 递归数据类型映射
`typescript
class Pattern extends DataModel {
@Mapping()
id?: number
@Mapping()
name?: string
@Mapping({ relatedEntityDescriptor: 'Pattern' })
pattern?: Pattern
}
const pattern = new Pattern()
pattern.id = 1
pattern.name = 'P1'
pattern.pattern = new Pattern()
pattern.pattern.id = 2
pattern.pattern.name = 'P2'
const data: model.Data = pattern.serialize()
expect(data.id).toBe(1)
expect(data.name).toBe('P1')
expect(data.pattern.id).toBe(2)
expect(data.pattern.name).toBe('P2')
`
- 将模型字段映射到多维数组
`typescript
class LogSource extends DataModel {
@Mapping()
name?: string
}
class Category extends DataModel {
@Mapping()
name?: string
}
class Rule extends DataModel {
@Mapping({ relatedEntityDescriptor: 'LogSource', path: 'filters[0][1]' })
logSource?: LogSource
@Mapping({ relatedEntityDescriptor: 'Category', path: 'filters[1][2]' })
category?: Category
}
const rule = new Rule()
rule.logSource = new LogSource()
rule.logSource.name = 'logSource'
rule.category = new Category()
rule.category.name = 'category'
const data: model.Data = rule.serialize()
expect(data.filters?.[0][1].name).toBe('logSource')
expect(data.filters?.[1][2].name).toBe('category')
`
- 将模型字段映射到对象中的数组切片
`typescript
class Foo extends DataModel {
@Mapping({ path: 'filters[0]' })
id?: number
@Mapping({ path: 'filters[1]' })
name?: string
@Mapping({ path: 'filters[2:3]' })
children?: string[]
}
const foo = new Foo()
foo.id = 1
foo.name = 'foo'
foo.children = ['Hello', 'World']
const data: model.Data = foo.serialize()
expect(data.filters).toHaveLength(3)
expect(data.filters[0]).toBe(1)
expect(data.filters[1]).toBe('foo')
expect(data.filters[2]).toBe('Hello')
expect(data.filters[3]).toBeUndefined()
`
- 将模型字段直接映射到数组切片
`typescript
class CategorySet extends DataModel {
@Mapping({ path: '[0]' })
id?: number
@Mapping({ path: '[1:]' })
categories?: string[]
}
const set = new CategorySet()
set.id = 1
set.categories = ['c1', 'c2', 'c3']
const data: model.Data = set.serialize([]) // !!important!! 注意, 这里需要提供初始化数据
expect(data[0]).toBe(1)
expect(data[1]).toBe('c1')
expect(data[2]).toBe('c2')
expect(data[3]).toBe('c3')
expect(data).toBeInstanceOf(Array)
`
用于自动序列化过程与反序列化过程中关联模型字段与映射数据的键
- @Mapping(options?: MappingOptions)
用来描述模型字段与映射数据关系的注解
`typescript`
type MappingOptions = {
/**
* 映射路径(JSONPath)
*/
path: string
/**
* 关联模型的名称
* 例如: Metric 表示指标, 或者 Metric[] 表示指标数组, 如果缺省则认为是一个 Object 或 Object[]
*/
relatedEntityDescriptor?: string
}
在序列化过程与反序列化过程后对模型中的数据进行断言
- @NotBeNull(msg?: string)
在 Serialize 或 Deserialize 时断言实例字段不会为 null, 断言失败时将 msg 信息以 throw new Error(msg) 的形式抛出.
- @NotBeUndefined(msg?: string)
在 Serialize 或 Deserialize 时断言实例字段不会为 undefined, 断言失败时将 msg 信息以 throw new Error(msg) 的形式抛出.
- @NotBeEmpty(msg?: string)
在 Serialize 或 Deserialize 时断言实例字段不会为 null 或 undefined, 断言失败时将 msg 信息以 throw new Error(msg) 的形式抛出. 等价于同时使用 @NotBeNull() @NotBeUndefined()
使用 @Mapping() 注解的自动序列化与反序列化无法覆盖某些特殊场景, 此时需要在DataModel派生类构造函数中编写自定义序列化函数(doSerialize)与反序列化函数(doDeSerialize). 一旦使用了自定义函数, 则忽略所有注解.
- 自定义序列化
`typescript
class Company extends DataModel {
constructor() {
super()
this.doSerialize = () => {
return { id: (this.id ?? 0) + 244 }
}
}
id?: number | null
}
const company = new Company()
company.id = 11
const data: model.Data = company.serialize()
expect(data.id).toBe(255)
`
- 自定义反序列化
`typescript
class Company extends DataModel {
constructor() {
super()
this.doDeSerialize = (data: model.Data) => {
this.id = data.aid
}
}
id?: number | null
}
const data: model.Data = { aid: 255 }
const company = new Company()
company.deserialize(data)
expect(company.id).toBe(255)
``