diff --git a/src/__tests__/fraction.spec.ts b/src/__tests__/fraction.spec.ts index ff61b11..c872cfc 100644 --- a/src/__tests__/fraction.spec.ts +++ b/src/__tests__/fraction.spec.ts @@ -725,3 +725,43 @@ describe('Fraction', () => { ); }); }); + +describe('JSON serialization', () => { + it('can serialize an array of fractions along with other data', () => { + const serialized = JSON.stringify([ + new Fraction(42), + 2, + new Fraction(-5, 3), + new Fraction('1.234'), + 'hello', + new Fraction({s: 0, n: 0, d: 1}), + ]); + + expect(serialized).toBe( + '[{"n":42,"d":1},2,{"n":-5,"d":3},{"n":617,"d":500},"hello",{"n":0,"d":1}]' + ); + }); + + it('can revive an array of fractions along with other data', () => { + const serialized = + '[{"n":42,"d":1},2,{"n":-5,"d":3},{"n":617,"d":500},"hello",{"n":0,"d":1}]'; + const data = JSON.parse(serialized, Fraction.reviver); + expect(data).toHaveLength(6); + + expect(data[0]).toBeInstanceOf(Fraction); + expect(data[0]).toEqual({s: 1, n: 42, d: 1}); + + expect(data[1]).toBe(2); + + expect(data[2]).toBeInstanceOf(Fraction); + expect(data[2]).toEqual({s: -1, n: 5, d: 3}); + + expect(data[3]).toBeInstanceOf(Fraction); + expect(data[3].equals('1.234')).toBe(true); + + expect(data[4]).toBe('hello'); + + expect(data[5]).toBeInstanceOf(Fraction); + expect(data[5]).toEqual({s: 0, n: 0, d: 1}); + }); +}); diff --git a/src/fraction.ts b/src/fraction.ts index 9fa9635..e640d7d 100644 --- a/src/fraction.ts +++ b/src/fraction.ts @@ -361,6 +361,44 @@ export class Fraction { return result + decimals + '...'; } + /** + * Serialize the fraction to a JSON compatible object. + * @returns An object with properties 'n', and 'd' corresponding to a signed numerator and an unsigned denominator respectively. + */ + toJSON(): UnsignedFraction { + return { + n: this.n * this.s, + d: this.d, + }; + } + + /** + * Revive a {@link Fraction} instance produced by Fraction.toJSON(). Return everything else as is. + * + * Intended usage: + * ```ts + * const data = JSON.parse(serializedData, Fraction.reviver); + * ``` + * + * @param key Property name. + * @param value Property value. + * @returns Deserialized {@link Fraction} instance or other data without modifications. + * @throws An error if the numerator or denominator exceeds `Number.MAX_SAFE_INTEGER`. + */ + static reviver(key: string, value: any) { + if ( + typeof value === 'object' && + 'n' in value && + Number.isInteger(value.n) && + 'd' in value && + Number.isInteger(value.d) && + Object.keys(value).length === 2 + ) { + return new Fraction(value as UnsignedFraction); + } + return value; + } + /** * Returns an array of continued fraction elements. *