@vlad_kramarukha/json-statham 1.0.0

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/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # @json-statham
2
+
package/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ /* Decorators */
2
+ import field from '@decorators/field.ts'
3
+ import skip from '@decorators/skip.ts'
4
+ import skipIf from '@decorators/skipIf.ts'
5
+ import schema from '@decorators/schema.ts'
6
+ import value from '@decorators/value.ts'
7
+ import mapper from '@decorators/mapper.ts'
8
+ import fieldName from '@decorators/fieldName.ts'
9
+
10
+ /* Mappers */
11
+ import { fromJson, toJson } from '@mappers'
12
+
13
+ export default { field, skip, skipIf, schema, value, mapper, fieldName, fromJson, toJson }
@@ -0,0 +1,7 @@
1
+ import Meta from '@meta'
2
+
3
+ export default function field(options: FieldOptions) {
4
+ return <Target extends object>(target: Target, property: keyof Target) => {
5
+ Meta.addMeta(target, property, options)
6
+ }
7
+ }
@@ -0,0 +1,5 @@
1
+ import field from '@decorators/field.ts'
2
+
3
+ export default function fieldName(fieldName: string, reconstruct = false) {
4
+ return field({ fieldName, reconstruct })
5
+ }
@@ -0,0 +1,5 @@
1
+ import field from '@decorators/field.ts'
2
+
3
+ export default function mapper<Record extends object>(mapper: Mapper<Record>) {
4
+ return field({ mapper })
5
+ }
@@ -0,0 +1,5 @@
1
+ import field from '@decorators/field.ts'
2
+
3
+ export default function schema(schema: FieldOptions['schema'], isArray: FieldOptions['isArray'] = false) {
4
+ return field({ schema, isArray })
5
+ }
@@ -0,0 +1,4 @@
1
+ import field from '@decorators/field.ts'
2
+
3
+ const skip = field({ skip: true })
4
+ export default skip
@@ -0,0 +1,5 @@
1
+ import field from '@decorators/field.ts'
2
+
3
+ export default function skipIf(skipMapper: SkipMapper) {
4
+ return field({ skip: skipMapper })
5
+ }
@@ -0,0 +1,5 @@
1
+ import field from '@decorators/field.ts'
2
+
3
+ export default function value(value: any) {
4
+ return field({ value })
5
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ interface Mapper<Record extends object> {
2
+ from?: (field: any, record: Record) => any
3
+ to?: (field: any, record: Record) => any
4
+ }
5
+
6
+ interface SkipMapper {
7
+ <Record>(field: any, record: Record): boolean
8
+ }
9
+
10
+ interface FieldOptions {
11
+ skip?: boolean | SkipMapper
12
+ value?: any
13
+ mapper?: Mapper
14
+ fieldName?: string
15
+ isArray?: boolean
16
+ reconstruct?: boolean
17
+ schema?: new () => object
18
+ }
19
+
20
+ interface MetaInfo {
21
+ [p: string]: FieldOptions | SimpleFieldOptions
22
+ }
package/lib/mappers.ts ADDED
@@ -0,0 +1,74 @@
1
+ import Meta from '@meta'
2
+
3
+ export function fromJson<Schema, Json extends object>(
4
+ schema: new () => Schema & MetaInfo,
5
+ json: Json
6
+ ): (Schema & MetaInfo) | Schema {
7
+ const result = {} as Record<string, any>
8
+ const instance = new schema()
9
+
10
+ const metaInfo = Meta.getMeta(instance)
11
+
12
+ for (const key of [...Object.keys(metaInfo), ...Object.keys(instance)]) {
13
+ const options = metaInfo[key]
14
+
15
+ if (!options) {
16
+ result[key] = json[key as keyof Json]
17
+ continue
18
+ }
19
+
20
+ const { fieldName, value, mapper, schema, isArray } = options as FieldOptions
21
+ const dataKey = fieldName !== undefined ? fieldName : key
22
+ const data = json[dataKey as keyof Json]
23
+
24
+ if (schema && data) {
25
+ result[key] = isArray ? (data as object[]).map((v: object) => fromJson(schema, v)) : fromJson(schema, data)
26
+ continue
27
+ }
28
+
29
+ if (mapper?.from) {
30
+ result[key] = mapper.from(data, json)
31
+ } else {
32
+ result[key] = data === undefined ? value : data
33
+ }
34
+ }
35
+
36
+ return Object.assign(instance, result)
37
+ }
38
+
39
+ export function toJson<Json extends object>(json: Json): Json {
40
+ const result = {} as Record<string, any>
41
+ const metaInfo = Meta.getMeta(json)
42
+
43
+ for (const key of [...Object.keys(metaInfo), ...Object.keys(json)]) {
44
+ const options = metaInfo[key]
45
+
46
+ if (!options) {
47
+ result[key] = json[key as keyof Json]
48
+ continue
49
+ }
50
+
51
+ const { mapper, schema, isArray, fieldName, reconstruct, skip } = options as FieldOptions
52
+ const data = json[key as keyof Json]
53
+
54
+ if (skip !== undefined) {
55
+ const skipped = typeof skip === 'function' ? skip(data, json) : skip
56
+ if (skipped) continue
57
+ }
58
+
59
+ const resultKey = fieldName && reconstruct ? fieldName : key
60
+
61
+ if (schema && data) {
62
+ result[resultKey] = isArray ? (data as object[]).map(toJson) : toJson(data)
63
+ continue
64
+ }
65
+
66
+ if (mapper?.to) {
67
+ result[resultKey] = mapper.to(data, json)
68
+ } else {
69
+ result[resultKey] = data
70
+ }
71
+ }
72
+
73
+ return result as Json
74
+ }
package/lib/meta.ts ADDED
@@ -0,0 +1,30 @@
1
+ const META_KEY = Symbol('meta')
2
+
3
+ const pt = (target: object) => target.constructor.prototype
4
+ const meta = (target: object) => pt(target)[META_KEY] || {}
5
+
6
+ export default class Meta {
7
+ static isMeta<Target extends object>(target: Target | (Target & MetaInfo)): target is Target & MetaInfo {
8
+ return META_KEY in pt(target)
9
+ }
10
+
11
+ static addMeta<Target extends object>(
12
+ target: Target,
13
+ property: keyof Target,
14
+ info: FieldOptions
15
+ ) {
16
+ if (!Meta.isMeta(target)) {
17
+ pt(target)[META_KEY] = {}
18
+ }
19
+
20
+ if (!meta(target)[property]) {
21
+ meta(target)[property] = {}
22
+ }
23
+
24
+ Object.assign(meta(target)[property], info)
25
+ }
26
+
27
+ static getMeta<Target extends object>(target: Target | Target & MetaInfo): MetaInfo {
28
+ return meta(target)
29
+ }
30
+ }
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@vlad_kramarukha/json-statham",
3
+ "version": "1.0.0",
4
+ "author": "vlad_kramarukha",
5
+ "private": false,
6
+ "description": "Mapper for convenient work with data",
7
+ "module": "index.ts",
8
+ "type": "module",
9
+ "license": "Apache-2.0",
10
+ "files": [
11
+ "index.ts",
12
+ "lib/*",
13
+ "README.md"
14
+ ],
15
+ "devDependencies": {
16
+ "@types/bun": "latest",
17
+ "prettier": "^3.8.1"
18
+ },
19
+ "peerDependencies": {
20
+ "typescript": "^5"
21
+ }
22
+ }