README
PodengJS
Simple JSON value normalization to make everything gone right.
This small library helps you to make JSON property value to follow the rules, no matter how strange the value is, you could still normalize it into right type(s).
Update: I have a limited amount of time to writing documentation on this module, for faster understanding (and example) please take a look at test files under test folder, it has a lot of code examples to get you understand the idea of this module.
Terms
- Normalization, convert json value to follow rules without changing any key/property name
- Serialization, normalization-like but with different key name
- Deserialization, reversed-like-serialization but with the different supplied key/property name
- Validation, validate json value if not matched with rules(also could work with serialization-deserialization)
Example Cases
- Parser-Serializer for your RESTful API
- JSON value validator
- JSON value normalization
- Minimalist JSON Schema
- Other...
Installation
Podeng requires a minimal node version >= 6.x
NPM
npm i podeng
Yarn
yarn add podeng
Known Dependencies
- lodash
- moment
- moment-timezone (for running test)
- jest (for running test)
Running the Test
>> npm i
>> npm run test
Documentation
Simple Example
const { blueprint, types } = require('podeng')
const PersonSchema = blueprint.object({
name: types.string,
age: types.integer,
birthday: types.datetime({ dateOnly: true }) // ISO 8601
})
const givenData = {
name: 'Aditya Kresna',
age: '27',
birthday: '1991-06-18 00:05:00',
address: 'Indonesia',
hobby: ['Sleeping', 'Coding']
}
const result = PersonSchema(givenData)
// {
// name: 'Aditya Kresna',
// age: 27,
// birthday: '1991-06-18'
// }
Blueprint Object Options
- frozen: Freeze the JSON result, so it cannot be modified
- giveWarning: Enable warning when parsing (normalize-serialize-deserialize) got invalid value
- onError: Object to determine action when error caught on parsing, this has 2 option handler,
onKey
andonAll
- onKey: A function to execute while some key detected has an invalid value, this function will 2 supplied by parameter key of type and error details
- onAll: A function to execute when all key detected has an invalid value, this function just supplied with one parameter, the error details
- throwOnError: Throwing an exception while the error parse detected
- allowUnknownProperties: This option will allow unknown properties on parsing
Blueprint Object Example
const Parser = blueprint.object(
{
key: types.bool,
key2: types.bool(['Yes', 'True', 'Yup'])
},
{
throwOnError: true,
giveWarning: true
}
);
const Parser2 = blueprint.object(
{
value: types.integer
},
{ onError: TypeError('The Invalid onError value') }
)
const Parser3 = blueprint.array(
{
value: types.integer
},
{
onError: {
onKey: (key, err) => {
throw new TypeError('Error coming from ', key)
},
},
}
)
// Create array object from JSON
const ListParser = blueprint.array({
name: types.string,
age: types.integer
})
// Create array object from existing blueprint object
const Person = blueprint.object({
name: types.string
});
const People = blueprint.array(Person);
Blueprint Extend
Once you make an Object of blueprint, it can be extended into some new object with different types. This method helps you easier to re-use an existing object or defining several object with same basic types.
Example Of Blueprint Extend
const { blueprint, types, BlueprintClass } = require('podeng');
const Car = blueprint.object({
color: types.string,
wheels: types.string({ normalize: ['trimmed', 'upper_first'] })
});
const Bus = blueprint.extend(Car, {
brand: types.string({ normalize: 'upper_first_word' }),
length: types.transform(val => `${val} meters`)
});
const Buses = blueprint.array(Bus);
// extend from an array object
const FlyingBuses = blueprint.extend(Buses, {
wingsCount: types.integer
});
Bus({
color: 'Blue',
wheels: 'bridgestone',
brand: 'mercedes benz',
length: 20
})
//{
// color: 'Blue',
// wheels: 'Bridgestone',
// brand: 'Mercedes Benz',
// length: '20 meters'
//}
FlyingBuses([{
color: 'Blue',
wheels: 'bridgestone',
brand: 'mercedes benz',
length: 20,
wingsCount: 20
}])
//[{
// color: 'Blue',
// wheels: 'Bridgestone',
// brand: 'Mercedes Benz',
// length: '20 meters'
// wingsCount: 20
//}]
Even you could attach some options like a normal blueprint object
const Man = blueprint.object({
name: types.string,
age: types.integer,
birthday: types.datetime({ dateOnly: true })
});
const Child = blueprint.extend(
Man,
{
toy: types.string
},
{
onError: new TypeError('Ooops you\'ve got an wrong value!')
}
);
Or you could remove properties that not needed on extended object by giving them an extra argument
const Man = blueprint.object({
name: types.string,
age: types.integer,
birthday: types.datetime({ dateOnly: true })
});
const Alien = blueprint.extend(Man, {
planet: types.string
}, {
deleteProperties: ['birthday']
})
Embedding Blueprint Object
In Podeng attaching an existing blueprint object to property is possible, it known as embedding object. On embed object, all the rules of blueprint object will be assigned to the related property in master object.
Example Embedded Object
const Obj1 = blueprint.object({
myValue: types.integer
})
const Obj2 = blueprint.object({
foo: types.string,
bar: Obj1
})
const Obj3 = blueprint.object({
foo: types.string,
bar: Obj1.embed({ default: 'empty value' })
})
Obj2({foo: 'bar', bar: { myValue: 'foo' }}) // { "foo": "bar", "bar": { "myValue": "foo" }}
Obj3({ foo: 'something', bar: { myValue: 'interesting' }}) // { "foo": "something", "bar": { "myValue": "interesting" }}
Obj3({ foo: 'something' }) // { "foo": "something", "bar": "empty value"}
Types options
Default Options (available for all types)
- hideOnFail: Hide the value when normalization failed, default:
false
- default: The default value to assign when normalization fails, default:
null
- validate: Custom validation function, should return boolean,
true
for valid,false
for invalid, default isnull
- serialize.to: Serialization options to rename property name on serializing, default:
null
- serialize.display: Serialization options to hide property name on serializing, default:
true
- deserialize.from: Deserialization options to accept property name on deserializing, default:
null
- deserialize.display: Deserialization options to hide property name on deserializing, default:
true
Type | Default Options | Description |
---|---|---|
string |
stringify: every value will be stringified, default: true min: minimum digits to accept, default: null max: maximum digits to accept, default: null normalize: normalize the string value, the valid options are:
|
String data types |
number |
min: minimum number value to accept, default: null max: maximum number value to accept, default: null minDigits: minimal digit number value to accept, not include the length behind of comma separated number, default: null maxDigits: maximum digit number value to accept, not include the length behind of comma separated number, default: null |
Number data types including float and integer |
integer |
min: minimum number value to accept, default: null max: maximum number value to accept, default: null minDigits: minimal digit value to accept, default: null maxDigits: maximum digit value to accept, default: null |
Accept integer data type |
float |
min: minimum number value to accept, default: null max: maximum number value to accept, default: null minDigits: minimal digit value to accept, not include the length after comma separated, default: null maxDigits: maximum digit value to accept, not include the length after comma separated, default: null |
Accept float data type |
bool |
validList: array list of valid values, default: null invalidList: array list of invalid values, default: null caseSensitive: case sensitive status to match with validList or invalidList, this works if any string value exist on these options, default: null normalizeNil: change every non undefined or null value to normalize as a true value, default: null |
In boolean data type, you should only choose either validList or invalidList to set on the options, they can't be used together (if both are setup, validList will be used). If you set validList , every value listed on validList array will make the result value become true , otherwise for every value described on invalidList array, every value that not listed on invalidList would become true . Please take a note for normalizeNil option doesn't effect if has validList or invalidList option |
datetime |
parseFormat: string format to parse or accept incoming value, default: null returnFormat: string format to parse value on before its returned, default: null timezoneAware: boolean that indicates to parse with current timezone or not, default: true asMoment: boolean that indicates to returning as a moment object, default: false dateOnly: only accept and return date format as YYYY-MM-DD , default: false timeOnly: only accept and return time format as HH:mm:ss , default: false |
Datetime type could be used for date or time implementation, they can be resulted as a moment object automatically |
any |
no options except on default options | All values will be accepted |
options |
list: array of valid values, or can be set directly as a type argument | Only value inside this list will evaluated as a valid value |
transform |
no options except on default options | transform accept function to evaluates the incoming value as a valid or invalid value, the function should return boolean type of status |
conditions |
evaluates: a function that evaluates the input value, must return boolean type indicating the success statusonOk: a function to run if the evaulation status is true onFail: a function to run if the evaulation status is false |
Condition could be helpful if we want to accept value based on several conditions shorthand: use first argument as evaluates function, second argument as onOk and third as onFail function. |
Types with Simple Example
These are various types that you can use to normalize JSON values, please take a note the example just a simple schema that may not covering all of the options described above, but you can explore more deeper inspecting the test files or by opening an issue here.
String
const Parser = blueprint.object({
name: types.string({ normalize: ['trimmed', 'upper_first_word']),
address: types.string({ normalize: 'trimmed'),
})
Parser({
name: ' aditya kresna permana ',
address: ' Bekasi Indonesia '
})
// produce { "name": 'Aditya Kresna Permana', address: 'Bekasi Indonesia' }
Integer
const Parser = blueprint.object({
num: types.integer({ min: 10 })
})
Parser({ num: 10 }) // { "num": 10 }
Parser({ num: 7 }) // { "num": null }
Float
const Parser = blueprint.object({
num: types.float({ min: 10 })
})
Parser({ num: 10.10 }) // { "num": 10.10 }
Parser({ num: 7.12 }) // { "num": null }
Number
const Parser = blueprint.object({
num: types.number,
num2: types.number
})
Parser({ num: 11.10, num2: 20 }) // { "num": 11.10, "num2": 20 }
Parser({ num: 7, num2: 18.5 }) // { "num": 7, "num2": 18.5 }
Boolean
const Parser = blueprint.object({
val1: types.bool,
val2: types.bool(['Yes', 'Yeah']),
})
Parser({ val1: true, val2: 'Yes' })) // { "val1": true, "val2": true }
Parser({ val1: true, val2: 'No' })) // { "val1": true, "val2": false }
DateTime
const Parser = blueprint.object({
val1: types.datetime,
val2: types.datetime('DD-MM-YYYY'),
})
Parser({ val1: '2018-06-18', val2: '18-06-1991' }) // { "val1": "2018-06-18T00:00:00+07:00", "val2": "1991-06-18T00:00:00+07:00" }
Options
const Car = blueprint.object({
brand: types.options(['Honda', 'Toyota', 'Mitsubishi']),
color: types.options({ list: ['Red', 'Green', 'Blue'], default: 'None' }),
})
Car({ brand: 'Yamaha', color: 'Green' }) // { "brand": null, "color": "Green" }
Car({ brand: 'Yamaha', color: 'Black' }) // { "brand": null, "color": "None" }
Transform
const Parser = blueprint.object({
val1: types.transform(val => val * 2),
val2: types.transform(1818),
})
Parser({ val1: 10, val2: 20 }) // { "val1": 20, "val2": 1818 }
Conditions
const Exercise = blueprint.object({
age: types.conditions(value => value >= 17, 'Adult', 'Child'),
grade: types.conditions({
evaluates: grade => grade >= 7,
onOk: 'Congratulation!',
onFail: 'Sorry You Fail',
}),
})
Exercise({ age: 17, grade: 8 }) // { "age": "Adult", "grade": "Congratulation!" }
Exercise({ age: 10, grade: 5 }) // { "age": "Child", "grade": "Sorry You Fail" }
Any
const Parser = blueprint.object({
val1: types.any,
val2: types.any(),
val3: types.any({ allowUndefined: true })
})
Parser({ val1: 123, val2: "Foo", val3: null }) // { "val1": 123, "val2": "Foo", "val3": null }
Parser({ val1: "Foo", val2: "Bar" }) // { "val1": 123, "val2": "Foo", "val3": undefined }
Parser({ val1: "Foo"}) // { "val1": 123, "val2": null, "val3": undefined }
Validator
Podeng has capability to validate your json, the validation refers to your type schema and throwing exception (or not) depending on what the options you set. If you find more example to implement validation, you could refers to test files as well.
Example Validation
const { blueprint, types, validator } = require('podeng');
const Human = blueprint.object({
eyeColor: types.string,
hairColor: types.string
});
const HumanValidator = validator(Human);
HumanValidator.validate({ eyeColor: 'Blue', hairColor: () => {} }); // will throw an error
const [errorStatus, errorDetails] = HumanValidator.check({
eyeColor: 'Blue',
hairColor: () => {}
});
// errorStatus: true
// errorDetails: { hairColor: ['failed to parse "hairColor" with its type'] }
License
MIT License
Copyright 2018 Aditya Kresna Permana
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.