diff --git a/packages/suntori/README.md b/packages/suntori/README.md index 66d0239..795a520 100644 --- a/packages/suntori/README.md +++ b/packages/suntori/README.md @@ -132,6 +132,8 @@ const json = serialize(doge) // json should be "equal" to dataSource. ## More decorators +There are also some additional decorators, providing extra functionalities. + ### @JsonString ```ts @@ -223,6 +225,29 @@ console.log(a.numNullable) // null console.log(a.numNullable2) // 0, only got null if payload were null. ``` +### @ParseInt and @ParseFloat + +```ts + @Serializable() + class A { + @JsonProperty() + @ParseInt() + readonly int: number = 0 + + @JsonProperty() + @ParseFloat() + readonly float: number = 0 + } + + const a = deserialize({ + int: '10', + float: '0.1' + }, A) + + console.log(a.int) // 10 + console.log(a.float) // 0.1 +``` + ## License Apache-2.0 diff --git a/packages/suntori/lib/config/meta.ts b/packages/suntori/lib/config/meta.ts index f63eadc..914c4ea 100644 --- a/packages/suntori/lib/config/meta.ts +++ b/packages/suntori/lib/config/meta.ts @@ -3,6 +3,8 @@ const META_KEY_SERIALIZABLE = 'suntori:serializable' const META_KEY_JSON_STRING = 'suntori:jsonString' const META_KEY_DYNAMIC_KEY = 'suntori:dynamicKey' const META_KEY_NULLABLE = 'suntori:nullable' +const META_KEY_PARSE_INT = 'suntori:parseInt' +const META_KEY_PARSE_FLOAT = 'suntori:parseFloat' const META_KEY_JSON_IGNORE = 'suntori:jsonIgnore' export { @@ -11,5 +13,7 @@ export { META_KEY_JSON_STRING, META_KEY_DYNAMIC_KEY, META_KEY_NULLABLE, + META_KEY_PARSE_INT, + META_KEY_PARSE_FLOAT, META_KEY_JSON_IGNORE } diff --git a/packages/suntori/lib/deserialize/decorator.ts b/packages/suntori/lib/deserialize/decorator.ts index a9f27e0..33b7673 100644 --- a/packages/suntori/lib/deserialize/decorator.ts +++ b/packages/suntori/lib/deserialize/decorator.ts @@ -1,4 +1,4 @@ -import { META_KEY_NULLABLE, META_KEY_DYNAMIC_KEY, META_KEY_JSON_STRING } from '../config/meta' +import { META_KEY_NULLABLE, META_KEY_DYNAMIC_KEY, META_KEY_JSON_STRING, META_KEY_PARSE_INT, META_KEY_PARSE_FLOAT } from '../config/meta' import { createPlainObject } from '../utils/object' /** @@ -113,6 +113,44 @@ function Nullable () { return defineDecorator(META_KEY_NULLABLE) } +/** + * A prop that decorated by this decorator will be parsed as int in force. + * + * @example + * @Serializable() + * class A { + * @JsonProperty() + * @ParseInt() + * readonly id: number = 0 + * } + * + * const a = deserialize({ + * id: '10' + * }, A) + */ +function ParseInt () { + return defineDecorator(META_KEY_PARSE_INT) +} + +/** + * A prop that decorated by this decorator will be parsed as float in force. + * + * @example + * @Serializable() + * class A { + * @JsonProperty() + * @ParseFloat() + * readonly value: number = 0 + * } + * + * const a = deserialize({ + * value: '10.24' + * }, A) + */ +function ParseFloat () { + return defineDecorator(META_KEY_PARSE_FLOAT) +} + /** * Check a meta registry has target prop assigned. * @@ -129,5 +167,7 @@ export { JsonString, DynamicKey, Nullable, + ParseInt, + ParseFloat, checkMetaRegistryHasProp } diff --git a/packages/suntori/lib/deserialize/index.ts b/packages/suntori/lib/deserialize/index.ts index 5fb2369..7d6b017 100644 --- a/packages/suntori/lib/deserialize/index.ts +++ b/packages/suntori/lib/deserialize/index.ts @@ -3,7 +3,9 @@ import { META_KEY_DYNAMIC_KEY, META_KEY_JSON_PROPERTY, META_KEY_JSON_STRING, - META_KEY_SERIALIZABLE + META_KEY_SERIALIZABLE, + META_KEY_PARSE_INT, + META_KEY_PARSE_FLOAT } from '../config/meta' import { ConstructorOf, IAllPropertiesMetaData, IJsonPropertyOption } from '../types' import { checkIsSerializable } from '../utils/meta' @@ -127,6 +129,21 @@ function createModelValueFromJson ( } if (expectedType === Number) { + const doIntParse = checkMetaRegistryHasProp(META_KEY_PARSE_INT, propName, instance) + const doFloatParse = checkMetaRegistryHasProp(META_KEY_PARSE_FLOAT, propName, instance) + + if (isString(payload)) { + if (doIntParse) { + const parsedValue = parseInt(payload) + return isNaN(parsedValue) ? fallbackValue : parsedValue + } + + if (doFloatParse) { + const parsedValue = parseFloat(payload) + return isNaN(parsedValue) ? fallbackValue : parsedValue + } + } + return isNumber(payload) ? payload : fallbackValue } diff --git a/packages/suntori/lib/index.ts b/packages/suntori/lib/index.ts index 699aff2..f7c0a0b 100644 --- a/packages/suntori/lib/index.ts +++ b/packages/suntori/lib/index.ts @@ -1,4 +1,4 @@ export { deserialize, Serializable, JsonProperty } from './deserialize' -export { JsonString, DynamicKey, Nullable } from './deserialize/decorator' +export { JsonString, DynamicKey, Nullable, ParseInt, ParseFloat } from './deserialize/decorator' export { serialize, JsonIgnore } from './serialize' export { cloneModel } from './utils/clone-model' diff --git a/packages/suntori/test/deserialize.spec.ts b/packages/suntori/test/deserialize.spec.ts index 5c16772..72586f1 100644 --- a/packages/suntori/test/deserialize.spec.ts +++ b/packages/suntori/test/deserialize.spec.ts @@ -1,33 +1,33 @@ import 'reflect-metadata' -import { deserialize, DynamicKey, JsonProperty, JsonString, Nullable, Serializable } from '../lib' +import { deserialize, DynamicKey, JsonProperty, JsonString, Nullable, ParseFloat, ParseInt, Serializable } from '../lib' describe('Deserialization testing.', () => { it('A simple deserialization should work properly.', () => { @Serializable() class Address { @JsonProperty('label') - label: string = '' + label = '' @JsonProperty('the_address') - address: string = '' + address = '' } @Serializable() class User { @JsonProperty() - name: string = '' + name = '' @JsonProperty() - age: number = 0 + age = 0 @JsonProperty() - date: Date = new Date() + date: Date = new Date() @JsonProperty('address') - address: Address = new Address() + address: Address = new Address() @JsonProperty('authors') - authors: string[] = [] + authors: string[] = [] } const instance = deserialize({ @@ -56,13 +56,13 @@ describe('Deserialization testing.', () => { @Serializable() class User { @JsonProperty() - name: string = '' + name = '' @JsonProperty('user_age') - age: number = 0 + age = 0 @JsonProperty('user_address') - address: string = 'Default address' + address = 'Default address' } const instance = deserialize({ @@ -80,13 +80,13 @@ describe('Deserialization testing.', () => { @Serializable() class User { @JsonProperty() - name: string = '' + name = '' @JsonProperty('user_age') - age: number = 0 + age = 0 @JsonProperty('user_address') - address: string = '' + address = '' } @Serializable() @@ -95,7 +95,7 @@ describe('Deserialization testing.', () => { name: 'users', type: User }) - users: User[] = [] + users: User[] = [] } const instance = deserialize({ @@ -112,8 +112,8 @@ describe('Deserialization testing.', () => { expect(instance).toEqual({ users: [ - {name: 'LancerComet', age: 100, address: 'The Mars.'}, - {name: 'John Smith', age: 200, address: 'Heaven.'} + { name: 'LancerComet', age: 100, address: 'The Mars.' }, + { name: 'John Smith', age: 200, address: 'Heaven.' } ] }) }) @@ -122,19 +122,19 @@ describe('Deserialization testing.', () => { @Serializable() class Creature { @JsonProperty('user_age') - age: number = 0 + age = 0 } @Serializable() class Person extends Creature { @JsonProperty() - name: string = '' + name = '' } @Serializable() class User extends Person { @JsonProperty('user_address') - address: string = '' + address = '' } const instance = deserialize({ @@ -154,13 +154,13 @@ describe('Deserialization testing.', () => { @Serializable() class User { @JsonProperty() - name: string = '' + name = '' @JsonProperty() - age: number = 0 + age = 0 @JsonProperty() - date: Date = new Date() + date: Date = new Date() } const userInstance = deserialize(null, User) @@ -172,7 +172,7 @@ describe('Deserialization testing.', () => { type: User, name: 'user_list' }) - userList: User[] = [userInstance] + userList: User[] = [userInstance] } const result = deserialize({ @@ -188,13 +188,13 @@ describe('Deserialization testing.', () => { @Serializable() class User { @JsonProperty() - name: string = '' + name = '' @JsonProperty() - age: number = 0 + age = 0 @JsonProperty() - date: Date = new Date() + date: Date = new Date() } const userInstance = deserialize(undefined, User) @@ -206,7 +206,7 @@ describe('Deserialization testing.', () => { type: User, name: 'user_list' }) - userList: User[] = [userInstance] + userList: User[] = [userInstance] } const result = deserialize({ @@ -222,7 +222,7 @@ describe('Deserialization testing.', () => { @Serializable() class Person { @JsonProperty() - name: string = '' + name = '' constructor (name) { this.name = name @@ -232,7 +232,7 @@ describe('Deserialization testing.', () => { @Serializable() class Country { @JsonProperty('chair_man') - chairman: Person = new Person('WINNIE') + chairman: Person = new Person('WINNIE') } const correctInstance = deserialize({ @@ -266,57 +266,57 @@ describe('Deserialization testing.', () => { @Serializable() class Sub { @JsonProperty('a_ee') - aEe: number = 0 + aEe = 0 @JsonProperty('b_cii') - bCii: number = 0 + bCii = 0 } @Serializable() class Params { @JsonProperty() - c: number = 0 + c = 0 @JsonProperty() - d: number = 0 + d = 0 } @Serializable() class A { @JsonProperty('sub_data') - subData: Sub = new Sub() + subData: Sub = new Sub() @JsonProperty() @JsonString() - params: Params = new Params() + params: Params = new Params() @JsonProperty() @JsonString() - paramsB: Params = new Params() + paramsB: Params = new Params() @JsonProperty('params_c') @JsonString() - paramsC: Params = new Params() + paramsC: Params = new Params() @JsonProperty('params_d') @JsonString() - paramsD: Params = new Params() + paramsD: Params = new Params() @JsonProperty({ type: Params, name: 'paramsE' }) - paramsE: Params[] = [new Params()] + paramsE: Params[] = [new Params()] @JsonProperty({ type: Params, name: 'paramsF' }) @JsonString() - paramsF: Params[] = [] + paramsF: Params[] = [] @JsonProperty('num') - allNum: number = 0 + allNum = 0 } const a = deserialize({ @@ -350,7 +350,7 @@ describe('Deserialization testing.', () => { @Serializable() class A { @JsonProperty() - a: string = '' + a = '' } @Serializable() @@ -360,7 +360,7 @@ describe('Deserialization testing.', () => { type: A }) @DynamicKey() - as: { [key: string]: A } = {} + as: { [key: string]: A } = {} } const b = deserialize({ @@ -380,7 +380,7 @@ describe('Deserialization testing.', () => { }) }) - it('@DisallowNull should work properly.', () => { + it('@Nullable should work properly.', () => { @Serializable() class SubA { @JsonProperty() @@ -431,4 +431,25 @@ describe('Deserialization testing.', () => { subANullable: null }) }) + + it('@ParseInt and @ParseFloat should work properly.', () => { + @Serializable() + class Test { + @JsonProperty() + @ParseInt() + readonly int: number = 1 + + @JsonProperty() + @ParseFloat() + readonly float: number = 0.1 + } + + expect(deserialize({ int: '10', float: '10.12' }, Test)).toEqual({ int: 10, float: 10.12 }) + expect(deserialize({ int: '-10', float: '-10.12' }, Test)).toEqual({ int: -10, float: -10.12 }) + expect(deserialize({ int: '20.1', float: '-100' }, Test)).toEqual({ int: 20, float: -100 }) + + // Checking fallback. + expect(deserialize({ int: 'abc', float: 'def' }, Test)).toEqual({ int: 1, float: 0.1 }) + expect(deserialize({ int: [], float: () => 100 }, Test)).toEqual({ int: 1, float: 0.1 }) + }) })