magic-structure 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/lib/decorators.d.ts +9 -0
- package/dist/lib/decorators.js +23 -0
- package/dist/lib/mappers.d.ts +2 -0
- package/dist/lib/mappers.js +63 -0
- package/dist/lib/meta.d.ts +6 -0
- package/dist/lib/meta.js +20 -0
- package/lib/decorators.ts +32 -0
- package/lib/mappers.ts +83 -0
- package/lib/meta.ts +32 -0
- package/lib/types.d.ts +22 -0
- package/package.json +2 -2
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FieldOptions, Mapper, SkipMapper } from './types.ts';
|
|
2
|
+
declare function field(options: FieldOptions): <Target extends object>(target: Target, property: keyof Target) => void;
|
|
3
|
+
declare function fieldName(fieldName: string, reconstruct?: boolean): <Target extends object>(target: Target, property: keyof Target) => void;
|
|
4
|
+
declare function mapper<Record extends object>(mapper: Mapper<Record>): <Target extends object>(target: Target, property: keyof Target) => void;
|
|
5
|
+
declare function schema(schema: FieldOptions['schema'], isArray?: FieldOptions['isArray']): <Target extends object>(target: Target, property: keyof Target) => void;
|
|
6
|
+
declare function value(value: any): <Target extends object>(target: Target, property: keyof Target) => void;
|
|
7
|
+
declare function skipIf(skipMapper: SkipMapper): <Target extends object>(target: Target, property: keyof Target) => void;
|
|
8
|
+
declare const skip: <Target extends object>(target: Target, property: keyof Target) => void;
|
|
9
|
+
export { field, fieldName, mapper, schema, value, skipIf, skip };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Meta from './meta';
|
|
2
|
+
function field(options) {
|
|
3
|
+
return (target, property) => {
|
|
4
|
+
Meta.addMeta(target, property, options);
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
function fieldName(fieldName, reconstruct = false) {
|
|
8
|
+
return field({ fieldName, reconstruct });
|
|
9
|
+
}
|
|
10
|
+
function mapper(mapper) {
|
|
11
|
+
return field({ mapper });
|
|
12
|
+
}
|
|
13
|
+
function schema(schema, isArray = false) {
|
|
14
|
+
return field({ schema, isArray });
|
|
15
|
+
}
|
|
16
|
+
function value(value) {
|
|
17
|
+
return field({ value });
|
|
18
|
+
}
|
|
19
|
+
function skipIf(skipMapper) {
|
|
20
|
+
return field({ skip: skipMapper });
|
|
21
|
+
}
|
|
22
|
+
const skip = field({ skip: true });
|
|
23
|
+
export { field, fieldName, mapper, schema, value, skipIf, skip };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import Meta from './meta';
|
|
2
|
+
export function fromJson(schema, json) {
|
|
3
|
+
if (json && typeof json !== 'object') {
|
|
4
|
+
return json;
|
|
5
|
+
}
|
|
6
|
+
const result = {};
|
|
7
|
+
const instance = new schema();
|
|
8
|
+
const metaInfo = Meta.getMeta(instance);
|
|
9
|
+
for (const key of [...Object.keys(metaInfo), ...Object.keys(instance)]) {
|
|
10
|
+
const options = metaInfo[key];
|
|
11
|
+
if (!options) {
|
|
12
|
+
result[key] = json[key];
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const { fieldName, value, mapper, schema, isArray } = options;
|
|
16
|
+
const dataKey = fieldName !== undefined ? fieldName : key;
|
|
17
|
+
const data = json[dataKey];
|
|
18
|
+
if (schema && data && typeof data === 'object') {
|
|
19
|
+
result[key] = isArray ? data.map((v) => fromJson(schema, v)) : fromJson(schema, data);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (mapper?.from) {
|
|
23
|
+
result[key] = mapper.from(data, json);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
result[key] = data === undefined ? value : data;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return Object.assign(instance, result);
|
|
30
|
+
}
|
|
31
|
+
export function toJson(json) {
|
|
32
|
+
if (json && typeof json !== 'object') {
|
|
33
|
+
return json;
|
|
34
|
+
}
|
|
35
|
+
const result = {};
|
|
36
|
+
const metaInfo = Meta.getMeta(json);
|
|
37
|
+
for (const key of [...Object.keys(metaInfo), ...Object.keys(json)]) {
|
|
38
|
+
const options = metaInfo[key];
|
|
39
|
+
if (!options) {
|
|
40
|
+
result[key] = json[key];
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const { mapper, schema, isArray, fieldName, reconstruct, skip } = options;
|
|
44
|
+
const data = json[key];
|
|
45
|
+
if (skip !== undefined) {
|
|
46
|
+
const skipped = typeof skip === 'function' ? skip(data, json) : skip;
|
|
47
|
+
if (skipped)
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const resultKey = fieldName && reconstruct ? fieldName : key;
|
|
51
|
+
if (schema && data && typeof data === 'object') {
|
|
52
|
+
result[resultKey] = isArray ? data.map(toJson) : toJson(data);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (mapper?.to) {
|
|
56
|
+
result[resultKey] = mapper.to(data, json);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
result[resultKey] = data;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FieldOptions, MetaInfo } from './types.ts';
|
|
2
|
+
export default class Meta {
|
|
3
|
+
static isMeta<Target extends object>(target: Target | (Target & MetaInfo)): target is Target & MetaInfo;
|
|
4
|
+
static addMeta<Target extends object>(target: Target, property: keyof Target, info: FieldOptions): void;
|
|
5
|
+
static getMeta<Target extends object>(target: Target | Target & MetaInfo): MetaInfo;
|
|
6
|
+
}
|
package/dist/lib/meta.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const META_KEY = Symbol('meta');
|
|
2
|
+
const pt = (target) => target.constructor.prototype;
|
|
3
|
+
const meta = (target) => pt(target)[META_KEY] || {};
|
|
4
|
+
export default class Meta {
|
|
5
|
+
static isMeta(target) {
|
|
6
|
+
return META_KEY in pt(target);
|
|
7
|
+
}
|
|
8
|
+
static addMeta(target, property, info) {
|
|
9
|
+
if (!Meta.isMeta(target)) {
|
|
10
|
+
pt(target)[META_KEY] = {};
|
|
11
|
+
}
|
|
12
|
+
if (!meta(target)[property]) {
|
|
13
|
+
meta(target)[property] = {};
|
|
14
|
+
}
|
|
15
|
+
Object.assign(meta(target)[property], info);
|
|
16
|
+
}
|
|
17
|
+
static getMeta(target) {
|
|
18
|
+
return meta(target);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FieldOptions, Mapper, SkipMapper } from './types.ts'
|
|
2
|
+
import Meta from './meta'
|
|
3
|
+
|
|
4
|
+
function field(options: FieldOptions) {
|
|
5
|
+
return <Target extends object>(target: Target, property: keyof Target) => {
|
|
6
|
+
Meta.addMeta(target, property, options)
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function fieldName(fieldName: string, reconstruct = false) {
|
|
11
|
+
return field({ fieldName, reconstruct })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function mapper<Record extends object>(mapper: Mapper<Record>) {
|
|
15
|
+
return field({ mapper })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function schema(schema: FieldOptions['schema'], isArray: FieldOptions['isArray'] = false) {
|
|
19
|
+
return field({ schema, isArray })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function value(value: any) {
|
|
23
|
+
return field({ value })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function skipIf(skipMapper: SkipMapper) {
|
|
27
|
+
return field({ skip: skipMapper })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const skip = field({ skip: true })
|
|
31
|
+
|
|
32
|
+
export { field, fieldName, mapper, schema, value, skipIf, skip }
|
package/lib/mappers.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { FieldOptions } from './types.ts'
|
|
2
|
+
import Meta from './meta'
|
|
3
|
+
|
|
4
|
+
export function fromJson<Schema extends object, Json extends object>(
|
|
5
|
+
schema: new () => Schema,
|
|
6
|
+
json: Json
|
|
7
|
+
): Schema {
|
|
8
|
+
if (json && typeof json !== 'object') {
|
|
9
|
+
return json
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const result = {} as Record<string, any>
|
|
13
|
+
const instance = new schema()
|
|
14
|
+
|
|
15
|
+
const metaInfo = Meta.getMeta(instance)
|
|
16
|
+
|
|
17
|
+
for (const key of [...Object.keys(metaInfo), ...Object.keys(instance)]) {
|
|
18
|
+
const options = metaInfo[key]
|
|
19
|
+
|
|
20
|
+
if (!options) {
|
|
21
|
+
result[key] = json[key as keyof Json]
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { fieldName, value, mapper, schema, isArray } = options as FieldOptions
|
|
26
|
+
const dataKey = fieldName !== undefined ? fieldName : key
|
|
27
|
+
const data = json[dataKey as keyof Json]
|
|
28
|
+
|
|
29
|
+
if (schema && data && typeof data === 'object') {
|
|
30
|
+
result[key] = isArray ? (data as object[]).map((v: object) => fromJson(schema, v)) : fromJson(schema, data)
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (mapper?.from) {
|
|
35
|
+
result[key] = mapper.from(data, json)
|
|
36
|
+
} else {
|
|
37
|
+
result[key] = data === undefined ? value : data
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return Object.assign(instance, result)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function toJson<Json extends object>(json: Json): Json {
|
|
45
|
+
if (json && typeof json !== 'object') {
|
|
46
|
+
return json
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = {} as Record<string, any>
|
|
50
|
+
const metaInfo = Meta.getMeta(json)
|
|
51
|
+
|
|
52
|
+
for (const key of [...Object.keys(metaInfo), ...Object.keys(json)]) {
|
|
53
|
+
const options = metaInfo[key]
|
|
54
|
+
|
|
55
|
+
if (!options) {
|
|
56
|
+
result[key] = json[key as keyof Json]
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { mapper, schema, isArray, fieldName, reconstruct, skip } = options as FieldOptions
|
|
61
|
+
const data = json[key as keyof Json]
|
|
62
|
+
|
|
63
|
+
if (skip !== undefined) {
|
|
64
|
+
const skipped = typeof skip === 'function' ? skip(data, json) : skip
|
|
65
|
+
if (skipped) continue
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const resultKey = fieldName && reconstruct ? fieldName : key
|
|
69
|
+
|
|
70
|
+
if (schema && data && typeof data === 'object') {
|
|
71
|
+
result[resultKey] = isArray ? (data as object[]).map(toJson) : toJson(data)
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (mapper?.to) {
|
|
76
|
+
result[resultKey] = mapper.to(data, json)
|
|
77
|
+
} else {
|
|
78
|
+
result[resultKey] = data
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result as Json
|
|
83
|
+
}
|
package/lib/meta.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FieldOptions, MetaInfo } from './types.ts'
|
|
2
|
+
|
|
3
|
+
const META_KEY = Symbol('meta')
|
|
4
|
+
|
|
5
|
+
const pt = (target: object) => target.constructor.prototype
|
|
6
|
+
const meta = (target: object) => pt(target)[META_KEY] || {}
|
|
7
|
+
|
|
8
|
+
export default class Meta {
|
|
9
|
+
static isMeta<Target extends object>(target: Target | (Target & MetaInfo)): target is Target & MetaInfo {
|
|
10
|
+
return META_KEY in pt(target)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static addMeta<Target extends object>(
|
|
14
|
+
target: Target,
|
|
15
|
+
property: keyof Target,
|
|
16
|
+
info: FieldOptions
|
|
17
|
+
) {
|
|
18
|
+
if (!Meta.isMeta(target)) {
|
|
19
|
+
pt(target)[META_KEY] = {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!meta(target)[property]) {
|
|
23
|
+
meta(target)[property] = {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Object.assign(meta(target)[property], info)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static getMeta<Target extends object>(target: Target | Target & MetaInfo): MetaInfo {
|
|
30
|
+
return meta(target)
|
|
31
|
+
}
|
|
32
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface Mapper<Record extends object = any> {
|
|
2
|
+
from?: (field: any, record: Record) => any
|
|
3
|
+
to?: (field: any, record: Record) => any
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface SkipMapper {
|
|
7
|
+
<Record>(field: any, record: Record): boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface FieldOptions<Record extends object = any> {
|
|
11
|
+
skip?: boolean | SkipMapper
|
|
12
|
+
value?: any
|
|
13
|
+
mapper?: Mapper<Record>
|
|
14
|
+
fieldName?: string
|
|
15
|
+
isArray?: boolean
|
|
16
|
+
reconstruct?: boolean
|
|
17
|
+
schema?: new () => object
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MetaInfo {
|
|
21
|
+
[p: string]: FieldOptions
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magic-structure",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"author": "Vlad Kramarukha",
|
|
5
5
|
"description": "TS lib for mapping data",
|
|
6
6
|
"keywords": ["TS", "mapper", "structure", "json", "magic"],
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "dist/index.js",
|
|
11
11
|
"types": "dist/index.d.ts",
|
|
12
|
-
"files": ["README.md", "LICENSE", "
|
|
12
|
+
"files": ["README.md", "LICENSE", "lib/*", "dist/*", "index.ts"],
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@types/bun": "latest"
|