Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docs/guides/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@ description: Serialization notes for @haskou/value-objects

# Serialization

Most value objects serialize through `valueOf()`.
Every value object implements `toJSON()` returning its primitive, so `JSON.stringify` emits clean values.

```typescript
JSON.stringify({
email: new Email('user@example.com'),
createdAt: new Timestamp(1782218400000),
});
// {"email":"user@example.com","createdAt":1782218400000}
```

`valueOf()` returns the same primitive.

```typescript
const email = new Email('user@example.com');
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/value-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Specialized Value Objects may normalize value comparison by overriding `hasValue
| `static enableNullObjectCreation()` | Restores automatic NullObject creation. |
| `valueOf()` | Returns the wrapped primitive value. Null objects return `undefined`. |
| `toString()` | Returns `valueOf().toString()`. |
| `toJSON()` | Returns the wrapped primitive, so `JSON.stringify` emits `"a@b.co"` instead of `{"value":"a@b.co"}`. |
| `hasValue(other)` | Compares only the wrapped value; accepts another Value Object or a primitive. |
| `isEqual(other)` | Returns true only for the same concrete Value Object class with an equal value. |
| `isNotEqual(other)` | Negates `isEqual()`. |
Expand All @@ -103,6 +104,8 @@ name.hasValue('hasko'); // true

## Notes

- `toString()` and `toJSON()` throw `NullObjectError` on null objects, like every other method.

- Use `isEqual()` for domain equality between Value Objects.
- Use `hasValue()` only when comparing the underlying value is intentional.
- Most concrete classes in this package extend `ValueObject` directly or indirectly.
Expand Down
4 changes: 4 additions & 0 deletions src/value-objects/ValueObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
Object.defineProperty(this, 'value', {
value,
writable: false,
configurable: false,

Check warning on line 29 in src/value-objects/ValueObject.ts

View workflow job for this annotation

GitHub Actions / Test and coverage

Expected "configurable" to come before "writable"
enumerable: true,
});
}
Expand Down Expand Up @@ -66,4 +66,8 @@
public toString(): string {
return this.value!.toString();
}

public toJSON(): T {
return this.value;
}
}
46 changes: 46 additions & 0 deletions tests/value-objects/Serialization.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
Coordinates,
Email,
NullObject,
NullObjectError,
NumberValueObject,
StringValueObject,
Timestamp,
} from '../../src';

describe('Serialization', () => {
it('should serialize primitives in JSON.stringify', () => {
const payload = {
coordinates: new Coordinates(1, 2),
email: new Email('a@b.co'),
number: new NumberValueObject(3),
timestamp: new Timestamp(1000),
};

expect(JSON.parse(JSON.stringify(payload))).toEqual({
coordinates: '1,2',
email: 'a@b.co',
number: 3,
timestamp: 1000,
});
});

it('should return the primitive from toJSON', () => {
expect(new StringValueObject('abc').toJSON()).toBe('abc');
expect(new NumberValueObject(7).toJSON()).toBe(7);
});

it('should keep toString as the string representation', () => {
expect(new NumberValueObject(7).toString()).toBe('7');
expect(new Coordinates(1, 2).toString()).toBe('1,2');
});

it('should throw NullObjectError on a NullObject', () => {
const nullObject = new StringValueObject(null as never);

expect(NullObject.isNullObject(nullObject)).toBeTrue();
expect(() => nullObject.toJSON()).toThrow(NullObjectError);
expect(() => nullObject.toString()).toThrow(NullObjectError);
expect(() => JSON.stringify({ nullObject })).toThrow(NullObjectError);
});
});
Loading