Last active
July 18, 2019 21:06
-
-
Save ksm2/aba18c9e891e8f681ccc1244b874cd6d to your computer and use it in GitHub Desktop.
Config Parser for Node.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Yaml from 'js-yaml' | |
import 'reflect-metadata' | |
export interface SettingOptions { | |
nullable?: boolean | |
} | |
export const Setting = ({ nullable = false }: SettingOptions = {}): PropertyDecorator => (target, propertyKey) => { | |
let properties: string[] = Reflect.getMetadata('config:settings', target) | |
if (!properties) { | |
properties = [] | |
Reflect.defineMetadata('config:settings', properties, target) | |
} | |
properties.push(propertyKey as string) | |
Reflect.defineMetadata('config:nullable', nullable, target, propertyKey) | |
} | |
export type ObjectType<T> = { new(): T }; | |
const toSnakeCase = (property: string): string => { | |
return property.replace(/[A-Z]/g, (s) => `_${s.toLowerCase()}`) | |
} | |
const ensureString = (value: unknown): string => { | |
if (typeof value === 'string') { | |
return value | |
} else if (typeof value === 'number') { | |
return String(value) | |
} | |
throw new Error('Cannot parse value as string: ' + value) | |
} | |
const ensureNumber = (value: unknown): number => { | |
if (typeof value === 'number') { | |
return value | |
} | |
throw new Error('Cannot parse value as number: ' + value) | |
} | |
const ensureBoolean = (value: unknown): boolean => { | |
if (typeof value === 'boolean') { | |
return value | |
} | |
throw new Error('Cannot parse value as boolean: ' + value) | |
} | |
const ensureDate = (value: unknown): Date => { | |
if (value instanceof Date) { | |
return value | |
} | |
throw new Error('Cannot parse value as date: ' + value) | |
} | |
export const parseConfig = <T extends object>(yaml: string, target: ObjectType<T>): T => { | |
const doc = Yaml.safeLoad(yaml) | |
console.log(doc) | |
return parseDoc(doc, target) | |
} | |
export const parseDoc = <T extends object>(doc: any, target: ObjectType<T>): T => { | |
const object = new target() | |
const properties: string[] = Reflect.getMetadata('config:settings', object) || [] | |
for (const property of properties) { | |
const subDoc = doc[toSnakeCase(property)] | |
const type = Reflect.getMetadata('design:type', object, property) | |
const nullable: boolean = Reflect.getMetadata('config:nullable', object, property) | |
const prop = (value: any) => Reflect.defineProperty(object, property, { value, enumerable: true }) | |
if ((typeof subDoc === 'undefined' || subDoc === null) && nullable) { | |
prop(undefined) | |
} else if (type === String) { | |
prop(ensureString(subDoc)) | |
} else if (type === Number) { | |
prop(ensureNumber(subDoc)) | |
} else if (type === Boolean) { | |
prop(ensureBoolean(subDoc)) | |
} else if (type === Date) { | |
prop(ensureDate(subDoc)) | |
} else { | |
console.log(property, type) | |
prop(parseDoc(subDoc, type)) | |
} | |
} | |
return object | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { parseConfig, Setting } from './configParser' | |
const yaml = ` | |
demo: | |
test: Hello | |
number: 42 | |
bool: ~ | |
hello_world: "demo" | |
date: 2019-07-18 | |
` | |
class Demo { | |
@Setting() | |
test!: string | |
@Setting() | |
number!: number | |
@Setting({ nullable: true }) | |
bool?: boolean | |
@Setting() | |
helloWorld!: string | |
} | |
class Config { | |
@Setting() | |
demo!: Demo | |
@Setting() | |
date!: Date | |
} | |
const config = parseConfig(yaml, Config) | |
console.dir(config) | |
if (config.demo.bool !== undefined) { | |
console.dir(config.demo.bool.toString()) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Yaml from 'js-yaml' | |
import 'reflect-metadata' | |
export interface SettingOptions { | |
nullable?: boolean | |
} | |
export const Setting = ({ nullable = false }: SettingOptions = {}): PropertyDecorator => (target, propertyKey) => { | |
let properties: string[] = Reflect.getMetadata('config:settings', target) | |
if (!properties) { | |
properties = [] | |
Reflect.defineMetadata('config:settings', properties, target) | |
} | |
properties.push(propertyKey as string) | |
Reflect.defineMetadata('config:nullable', nullable, target, propertyKey) | |
} | |
export type ObjectType<T> = { new(): T }; | |
const toSnakeCase = (property: string): string => { | |
return property.replace(/[A-Z]/g, (s) => `_${s.toLowerCase()}`) | |
} | |
const ensureString = (value: unknown): string => { | |
if (typeof value === 'string') { | |
return value | |
} else if (typeof value === 'number') { | |
return String(value) | |
} | |
throw new Error('Cannot parse value as string: ' + value) | |
} | |
const ensureNumber = (value: unknown): number => { | |
if (typeof value === 'number') { | |
return value | |
} | |
throw new Error('Cannot parse value as number: ' + value) | |
} | |
const ensureBoolean = (value: unknown): boolean => { | |
if (typeof value === 'boolean') { | |
return value | |
} | |
throw new Error('Cannot parse value as boolean: ' + value) | |
} | |
const ensureDate = (value: unknown): Date => { | |
if (value instanceof Date) { | |
return value | |
} | |
throw new Error('Cannot parse value as date: ' + value) | |
} | |
export const parseConfig = <T extends object>(yaml: string, target: ObjectType<T>): T => { | |
const doc = Yaml.safeLoad(yaml) | |
console.log(doc) | |
return parseDoc(doc, target) | |
} | |
export const parseDoc = <T extends object>(doc: any, target: ObjectType<T>): T => { | |
const object = new target() | |
const properties: string[] = Reflect.getMetadata('config:settings', object) || [] | |
for (const property of properties) { | |
const subDoc = doc[toSnakeCase(property)] | |
const type = Reflect.getMetadata('design:type', object, property) | |
const nullable: boolean = Reflect.getMetadata('config:nullable', object, property) | |
const prop = (value: any) => Reflect.defineProperty(object, property, { value, enumerable: true }) | |
if ((typeof subDoc === 'undefined' || subDoc === null) && nullable) { | |
prop(undefined) | |
} else if (type === String) { | |
prop(ensureString(subDoc)) | |
} else if (type === Number) { | |
prop(ensureNumber(subDoc)) | |
} else if (type === Boolean) { | |
prop(ensureBoolean(subDoc)) | |
} else if (type === Date) { | |
prop(ensureDate(subDoc)) | |
} else { | |
console.log(property, type) | |
prop(parseDoc(subDoc, type)) | |
} | |
} | |
return object | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment