typedconverter
Advanced tools
Comparing version 1.0.0-alpha.3 to 1.0.0-alpha.4
@@ -1,24 +0,26 @@ | ||
interface ConverterMap { | ||
key: Function; | ||
converter: Converter; | ||
declare type ConverterMap = [Function | string, Converter]; | ||
interface ConversionIssue { | ||
path: string[]; | ||
messages: string[]; | ||
} | ||
declare type Converter = (value: any, path: string[], expectedType: Function | Function[], converters: Map<Function | string, Converter>) => any; | ||
declare type ConversionResult = [any, ConversionIssue[] | undefined]; | ||
declare type ConverterStore = Map<Function | string, Converter>; | ||
interface ObjectInfo<T> { | ||
path: string[]; | ||
expectedType: T; | ||
converters: ConverterStore; | ||
} | ||
declare type Converter = (value: any, info: ObjectInfo<Function | Function[]>) => ConversionResult; | ||
declare class ConversionError extends Error { | ||
issues: { | ||
path: string[]; | ||
messages: string[]; | ||
}; | ||
issues: ConversionIssue[]; | ||
status: number; | ||
constructor(issues: { | ||
path: string[]; | ||
messages: string[]; | ||
}, status?: number); | ||
constructor(issues: ConversionIssue[], status?: number); | ||
} | ||
declare namespace DefaultConverters { | ||
function booleanConverter(rawValue: {}, path: string[]): boolean; | ||
function numberConverter(rawValue: {}, path: string[]): number; | ||
function dateConverter(rawValue: {}, path: string[]): Date; | ||
function modelConverter(value: {}, path: string[], expectedType: Function | Function[], converters: Map<Function | string, Converter>): any; | ||
function arrayConverter(value: {}, path: string[], expectedType: Function[], converters: Map<Function | string, Converter>): any; | ||
function friendlyArrayConverter(value: {}, path: string[], expectedType: Function[], converters: Map<Function | string, Converter>): any; | ||
function booleanConverter(rawValue: {}, info: ObjectInfo<Function | Function[]>): ConversionResult; | ||
function numberConverter(rawValue: {}, info: ObjectInfo<Function | Function[]>): ConversionResult; | ||
function dateConverter(rawValue: {}, info: ObjectInfo<Function | Function[]>): ConversionResult; | ||
function modelConverter(value: {}, info: ObjectInfo<Function | Function[]>): ConversionResult; | ||
function strictArrayConverter(value: {}, info: ObjectInfo<Function[]>): ConversionResult; | ||
function friendlyArrayConverter(value: {}, info: ObjectInfo<Function[]>): ConversionResult; | ||
} | ||
@@ -30,3 +32,3 @@ declare function converter(option?: { | ||
}): (value: any, type?: Function | Function[] | undefined, path?: string[]) => any; | ||
export { Converter, DefaultConverters, ConverterMap, ConversionError }; | ||
export { Converter, DefaultConverters, ConverterMap, ConversionResult, ConversionError, ObjectInfo, ConversionIssue, ConverterStore }; | ||
export default converter; |
112
lib/index.js
@@ -7,3 +7,3 @@ "use strict"; | ||
constructor(issues, status = 400) { | ||
super(); | ||
super("Conversion error"); | ||
this.issues = issues; | ||
@@ -20,3 +20,3 @@ this.status = status; | ||
const typeName = Array.isArray(typ) ? `Array<${typ[0].name}>` : typ.name; | ||
return new ConversionError({ path: path, messages: [`Unable to convert "${value}" into ${typeName}`] }); | ||
return [{ path: path, messages: [`Unable to convert "${value}" into ${typeName}`] }]; | ||
} | ||
@@ -51,3 +51,3 @@ //some object can't simply convertible to string https://github.com/emberjs/ember.js/issues/14922#issuecomment-278986178 | ||
(function (DefaultConverters) { | ||
function booleanConverter(rawValue, path) { | ||
function booleanConverter(rawValue, info) { | ||
const value = safeToString(rawValue); | ||
@@ -60,23 +60,24 @@ const list = { | ||
if (result === undefined) | ||
throw createConversionError(value, Boolean, path); | ||
return result; | ||
return [undefined, createConversionError(value, Boolean, info.path)]; | ||
return [result, undefined]; | ||
} | ||
DefaultConverters.booleanConverter = booleanConverter; | ||
function numberConverter(rawValue, path) { | ||
function numberConverter(rawValue, info) { | ||
const value = safeToString(rawValue); | ||
const result = Number(value); | ||
if (isNaN(result) || value === "") | ||
throw createConversionError(value, Number, path); | ||
return result; | ||
return [undefined, createConversionError(value, Number, info.path)]; | ||
return [result, undefined]; | ||
} | ||
DefaultConverters.numberConverter = numberConverter; | ||
function dateConverter(rawValue, path) { | ||
function dateConverter(rawValue, info) { | ||
const value = safeToString(rawValue); | ||
const result = new Date(value); | ||
if (isNaN(result.getTime()) || value === "") | ||
throw createConversionError(value, Date, path); | ||
return result; | ||
return [undefined, createConversionError(value, Date, info.path)]; | ||
return [result, undefined]; | ||
} | ||
DefaultConverters.dateConverter = dateConverter; | ||
function modelConverter(value, path, expectedType, converters) { | ||
function modelConverter(value, info) { | ||
const { converters } = info; | ||
//--- helper functions | ||
@@ -87,3 +88,3 @@ const isConvertibleToObject = (value) => typeof value !== "boolean" | ||
//--- | ||
const TheType = expectedType; | ||
const TheType = info.expectedType; | ||
//get reflection metadata of the class | ||
@@ -93,8 +94,15 @@ const reflection = tinspector_1.default(TheType); | ||
if (!isConvertibleToObject(value)) | ||
throw createConversionError(value, TheType, path); | ||
return [undefined, createConversionError(value, TheType, info.path)]; | ||
const instance = createInstance(value, TheType); | ||
//traverse through the object properties and convert to appropriate property's type | ||
//sanitize excess property to prevent object having properties that doesn't match with declaration | ||
const issues = []; | ||
for (let x of reflection.properties) { | ||
const val = convert(value[x.name], path.concat(x.name), x.type, converters); | ||
const [val, err] = convert(value[x.name], { | ||
path: info.path.concat(x.name), | ||
expectedType: x.type, | ||
converters | ||
}); | ||
if (err) | ||
issues.push(...err); | ||
//remove undefined properties | ||
@@ -106,14 +114,34 @@ if (val === undefined) | ||
} | ||
return instance; | ||
if (issues.length > 0) | ||
return [undefined, issues]; | ||
else | ||
return [instance, undefined]; | ||
} | ||
DefaultConverters.modelConverter = modelConverter; | ||
function arrayConverter(value, path, expectedType, converters) { | ||
function arrayConverter(value, info) { | ||
const result = value.map((x, i) => convert(x, { | ||
path: info.path.concat(i.toString()), | ||
expectedType: info.expectedType && info.expectedType[0], | ||
converters: info.converters | ||
})); | ||
if (result.some(x => !!x[1])) { | ||
const issue = []; | ||
for (const [, err] of result) { | ||
if (err) | ||
issue.push(...err); | ||
} | ||
return [undefined, issue]; | ||
} | ||
else | ||
return [result.map(x => x[0]), undefined]; | ||
} | ||
function strictArrayConverter(value, info) { | ||
if (!Array.isArray(value)) | ||
throw createConversionError(value, expectedType, path); | ||
return value.map((x, i) => convert(x, path.concat(i.toString()), expectedType[0], converters)); | ||
return [undefined, createConversionError(value, info.expectedType, info.path)]; | ||
return arrayConverter(value, info); | ||
} | ||
DefaultConverters.arrayConverter = arrayConverter; | ||
function friendlyArrayConverter(value, path, expectedType, converters) { | ||
DefaultConverters.strictArrayConverter = strictArrayConverter; | ||
function friendlyArrayConverter(value, info) { | ||
const cleanValue = Array.isArray(value) ? value : [value]; | ||
return cleanValue.map((x, i) => convert(x, path.concat(i.toString()), expectedType[0], converters)); | ||
return arrayConverter(cleanValue, info); | ||
} | ||
@@ -126,34 +154,40 @@ DefaultConverters.friendlyArrayConverter = friendlyArrayConverter; | ||
// --------------------------------------------------------------------- // | ||
function convert(value, path, expectedType, converters) { | ||
function convert(value, info) { | ||
const { expectedType, converters, path } = info; | ||
if (value === null || value === undefined) | ||
return undefined; | ||
return [undefined, undefined]; | ||
if (!expectedType) | ||
return value; | ||
return [value, undefined]; | ||
if (expectedType === Object) | ||
return value; | ||
return [value, undefined]; | ||
if (value.constructor === expectedType) | ||
return value; | ||
return [value, undefined]; | ||
//check if the parameter contains @array() | ||
if (Array.isArray(expectedType)) | ||
return converters.get("Array")(value, path, expectedType, converters); | ||
return converters.get("Array")(value, { expectedType, converters, path }); | ||
//check if parameter is native value that has default converter (Number, Date, Boolean) or if user provided converter | ||
else if (converters.has(expectedType)) | ||
return converters.get(expectedType)(value, path, expectedType, converters); | ||
return converters.get(expectedType)(value, { expectedType, converters, path }); | ||
//if type of model and has no converter, use DefaultObject converter | ||
else | ||
return converters.get("Model")(value, path, expectedType, converters); | ||
return converters.get("Model")(value, { expectedType, converters, path }); | ||
} | ||
function converter(option = {}) { | ||
return (value, type, path = []) => { | ||
const mergedConverters = new Map(); | ||
mergedConverters.set(Number, DefaultConverters.numberConverter); | ||
mergedConverters.set(Boolean, DefaultConverters.booleanConverter); | ||
mergedConverters.set(Date, DefaultConverters.dateConverter); | ||
const arrayConverter = (option.guessArrayElement ? DefaultConverters.friendlyArrayConverter : DefaultConverters.arrayConverter); | ||
mergedConverters.set("Array", arrayConverter); | ||
mergedConverters.set("Model", DefaultConverters.modelConverter); | ||
(option.converters || []).forEach(x => mergedConverters.set(x.key, x.converter)); | ||
return convert(value, path, type || option.type, mergedConverters); | ||
const arrayConverter = (option.guessArrayElement ? DefaultConverters.friendlyArrayConverter : DefaultConverters.strictArrayConverter); | ||
const mergedConverters = new Map([ | ||
[Number, DefaultConverters.numberConverter], | ||
[Boolean, DefaultConverters.booleanConverter], | ||
[Date, DefaultConverters.dateConverter], | ||
["Array", arrayConverter], | ||
["Model", DefaultConverters.modelConverter], | ||
...option.converters || [] | ||
]); | ||
const [val, err] = convert(value, { path, expectedType: type || option.type, converters: mergedConverters }); | ||
if (err) | ||
throw new ConversionError(err); | ||
else | ||
return val; | ||
}; | ||
} | ||
exports.default = converter; |
{ | ||
"name": "typedconverter", | ||
"version": "1.0.0-alpha.3", | ||
"version": "1.0.0-alpha.4", | ||
"description": "Convert object into classes match with TypeScript type annotation", | ||
@@ -13,3 +13,5 @@ "main": "lib/index.js", | ||
"test": "jest", | ||
"prepublish": "tsc -p tsconfig.build.json" | ||
"prepublish": "tsc -p tsconfig.build.json", | ||
"gen": "ts-node src/generator.ts", | ||
"clean": "rm -rf src/*.js && rm -rf test/*.js" | ||
}, | ||
@@ -19,9 +21,15 @@ "author": "Ketut Sandiarsa", | ||
"dependencies": { | ||
"tinspector": "^2.2.0", | ||
"tslib": "^1.9.3" | ||
"@types/validator": "^10.11.0", | ||
"tinspector": "^2.2.1", | ||
"tslib": "^1.9.3", | ||
"validator": "^10.11.0" | ||
}, | ||
"devDependencies": { | ||
"@types/ejs": "^2.6.3", | ||
"@types/jest": "^24.0.11", | ||
"@types/joi": "^14.3.2", | ||
"coveralls": "^3.0.3", | ||
"ejs": "^2.6.1", | ||
"jest": "^24.5.0", | ||
"joi": "^14.3.1", | ||
"ts-jest": "^24.0.0", | ||
@@ -28,0 +36,0 @@ "typescript": "^3.3.4000" |
@@ -5,3 +5,3 @@ # TypedConverter | ||
[![Build Status](https://travis-ci.org/plumier/typedconverter.svg?branch=master)](https://travis-ci.org/plumier/typedconverter) | ||
[![Coverage Status](https://coveralls.io/repos/github/plumier/typedconverter/badge.svg?branch=master)](https://coveralls.io/github/plumier/typedconverter?branch=master) | ||
[![Coverage Status](https://coveralls.io/repos/github/plumier/typedconverter/badge.svg?branch=master)](https://coveralls.io/github/plumier/typedconverter?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/plumier/typedconverter.svg)](https://greenkeeper.io/) | ||
@@ -41,3 +41,3 @@ | ||
reflect.parameterProperties() | ||
@reflect.parameterProperties() | ||
class AnimalClass { | ||
@@ -70,2 +70,29 @@ constructor( | ||
## Convert Child Array | ||
Nested child array need to be decorate for TypeScript added design data type | ||
```typescript | ||
import createConverter from "typedconverter"; | ||
@reflect.parameterProperties() | ||
class Tag { | ||
constructor( | ||
public name: string, | ||
) { } | ||
} | ||
@reflect.parameterProperties() | ||
class Animal { | ||
constructor( | ||
public name: string, | ||
@reflect.array(Tag) | ||
public tags: Tags | ||
) { } | ||
} | ||
const convert = createConverter() | ||
//tags is instance of Tag class | ||
const numb = convert({name: "Mimi", tags: [{name: "Susi"}, {name: "Lorem"}]}, Animal) | ||
``` | ||
## Custom Converter | ||
@@ -83,1 +110,10 @@ Provided custom converter on the configuration | ||
``` | ||
## Guess Array Element | ||
Useful when converting data from url encoded, where single value could be a single array. | ||
```typescript | ||
const convert = createConverter({ guessArrayElement: true }) | ||
const b = convert("1", [Number]) //ok | ||
``` | ||
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17632
7
288
116
4
9
1
+ Added@types/validator@^10.11.0
+ Addedvalidator@^10.11.0
+ Added@types/validator@10.11.3(transitive)
+ Addedvalidator@10.11.0(transitive)
Updatedtinspector@^2.2.1