value-object.js
Value Object
- objects that matter only as the combination of their
properties. Two value objects with the same values for all their properties are
considered equal.
This library provides a convenient way to define strict, immutable value objects.
Install
npm install value-object
Defining value objects
Use subclasses to define value objects with type constraints:
const ValueObject = {} {}
...or don't use classes, if you prefer:
const Money = ValueObject
Instantiating value objects
Use the new
keyword, passing values for each property:
const gbp = code: 'GBP' name: 'British Pounds' const price = currency: gbp amount: 1234 const other = currency: code: 'USD' name: 'US Dollars' amount: 1456
Constraints prevent value objects from being instantiated with invalid property values.
Unexpected types
Property values with unexpected types are rejected:
> code: 'USD' name: 123
Error: Currency was constructed with invalid property values
Expected: { code:string, name:string }
Actual: { code:string, name:number }
name is invalid:
Expected string, was number
Unrecognised properties
Value objects cannot be instantiated with unrecognised properties:
> code: 'NZD' name: 'New Zealand Dollars' colour: 'All black'
Error: Currency was constructed with invalid property values
Expected: { code:string, name:string }
Actual: { code:string, name:string, colour:string }
colour is invalid:
Property is unexpected
Missing properties
Value objects cannot be instantiated with missing properties (unless they are optional):
> amount: 123
Error: Money was constructed with invalid property values
Expected: { currency:Currency, amount:number }
Actual: { amount:number }
currency is invalid:
Property is missing
null
Setting properties to Properties can be set to null
:
> currency: null amount: null
Money { currency: null, amount: null }
undefined
Setting properties to Properties cannot be set to undefined
(unless they are optional):
> currency: null amount: undefined
Error: Money was constructed with invalid property values
Expected: { currency:Currency, amount:number }
Actual: { currency:null, amount:undefined }
amount is invalid:
Expected number, was undefined
Built-in property types
Properties can be declared with built-in type constraints:
{}
string
: expects a value wheretypeof value === 'string'
number
: expects a value wheretypeof value === 'number'
boolean
: expects a value wheretypeof value === 'boolean'
object
: expects a value wheretypeof value === 'object'
any
: expects any non-null value
Optional properties
Properties declared with ?
can be set to null
or undefined
, or omitted
altogether:
{} age: null aliases: {} colour: undefined // => Options { age: null, aliases: {}, colour: undefined }
Optional properties can also be declared with ValueObject.optional()
:
{} flavours: 'mint' 'chocolate' // => IceCream { flavours: [ 'mint', 'chocolate' ] } {}// => IceCream {}
Array properties
Arrays with arbitrary elements can be declared with the type Array
:
{} favouriteThings: 'cheese' 69 null
Generic array properties
Arrays with value constraints are declared by wrapping the type definition (e.g.
'number'
, Date
) in []
:
{} {} vertices: x: 1 y: 2 x: 3 y: 4 }
User-defined properties
Custom property types can be defined with ValueObject.definePropertyType()
and
then used later by name in ValueObject.define()
:
ValueObject {}
Property constraints are expressed as a function that returns a value with the following methods:
.coerce(value)
converts an arbitrary value to the final property value. Expected to return{ value }
when converting the property value is successful or{ failure }
with a message when converting fails..areEqual(a, b)
returnstrue
if two instances of the type are considered equal, orfalse
otherwise..describe()
returns a string used in error messages mentioning the property.
The constraint is used to convert property values from other types according to its
.coerce(value)
method:
> cash: '123.00 GBP'
Allowance { cash: { amount: 123, currency: 'GBP' } }
...and its .describe()
method is used in error messages:
> cash: 666
Error: Allowance was constructed with invalid property values
Expected: { cash:<money> }
Actual: { cash:number }
cash is invalid:
Only string values allowed
Equality
Value objects are considered to be equal if their properties are equal. Equality
of two objects is tested by calling valueObject.isEqualTo(otherValueObject)
:
gbp// => true gbp// => false const gbpPrice = amount: 123 currency: gbp const eurPrice = amount: 123 currency: eur gbpPrice// => false eurPrice// => true
Reflection
ValueObject types have a schema
property that allows reflection over
properties and arbitrary metadata associated with each property:
{} > ProductschemapropertiesstockLevel
Property {
constraint: Primitive { cast: [Function: Number], name: 'number' },
metadata: { default: 0, description: 'units in stock' },
optional: false }
Creating new value objects from existing value objects
Use with(newAttributes)
to create new value objects, with new values for a
specific set of properties:
const salePrice = pricesalePricecurrencycode// => 'GBP'
Converting value objects to plain objects
Use toPlainObject()
to create a plain old mutable object from a value object's
property values:
> JSON
Any value-object instances will be converted using their schemas. Any objects
that are not value-object instances will be cloned using
JSON.parse(JSON.stringify(object))
by default. Pass in an optional clone
function to override this behaviour:
valueObject
Converting value objects to JSON
Use toJSON()
to create an object with __type__
properties for subsequent
deserialization:
> JSON
Converting value objects from JSON
Use ValueObject.deserializeForNamespaces()
to create a deserialize function
that can turn the resulting JSON string back into objects
const deserialize = ValueObjectconst gbp2 = gbp2// => true
Immutability
Value objects cannot be updated. Use strict mode to throw errors when attempts to set property values are made.
gbpcode = 'USD'// TypeError:Cannot assign to read only property 'amount' of object '#<Currency>