diff --git a/README.md b/README.md index 2c8de16..0d7181e 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,48 @@ const result = deserialize(json, { Record types can be named. This is utilized by the serializer/deserializer to revive `immutable.Record` objects. See the `SampleRecord` name passed into `immutable.Record()` as the second argument. -NOTE: When an unknown record type is encountered during deserialization, an error is thrown. +### Unknown Records + +```javascript +const SampleRecord = immutable.Record( + { 'a': 3, 'b': 4 }, + 'SampleRecord' +) + +const data = { + 'x': SampleRecord({ 'a': 5 }), +} + +// Serialize +const json = serialize(data) +// json == '{"x":{"__record":"SampleRecord","data":{"a":5}}}' + +// Deserialize +const result = deserialize(json, { + parseUnknownRecords: true +}) +``` + +```javascript +const UnnamedRecord = immutable.Record( + { 'a': 3, 'b': 4 } +) + +const data = { + 'x': UnnamedRecord({ 'a': 5 }), +} + +// Serialize +const json = serialize(data) +// json == '{"x":{"a":5}}' +const json2 = serialize(data, { storeUnknownRecords: true }) +// json == '{"x":{"__record":"__unknown","data":{"a":5}}}' + +// Deserialize +const result = deserialize(json2, { + parseUnknownRecords: true +}) +``` ### General Immutable Structures @@ -96,6 +137,7 @@ NOTE: When an unknown Immutable iterable type is encountered during deserializat - `data`: The data to serialize. - `options={}`: Serialization options. - `pretty=false`: Whether to pretty-print the result (2 spaces). + - `storeUnknownRecords=false`: Whether to save unnamed records as objects or records Return value: @@ -108,6 +150,7 @@ NOTE: When an unknown Immutable iterable type is encountered during deserializat - `json`: A JSON representation of data. - `options={}`: Deserialization options. - `recordTypes={}`: `immutable.Record` factories. + - `parseUnknownRecords=true`: deserialize unknown `immutable.Record` Return value: diff --git a/src/deserialize.js b/src/deserialize.js index 3526f25..a34fcf7 100644 --- a/src/deserialize.js +++ b/src/deserialize.js @@ -27,8 +27,12 @@ function revive(key, value, options) { function reviveRecord(key, recInfo, options) { - const RecordType = options.recordTypes[recInfo['__record']] + const RecordType = options.recordTypes && options.recordTypes[recInfo['__record']] if (!RecordType) { + if (options.parseUnknownRecords) { + var TmpRecordType = new immutable.Record(recInfo['data']); + return TmpRecordType(revive(key, recInfo['data'], options)) + } throw new Error(`Unknown record type: ${recInfo['__record']}`) } diff --git a/src/serialize.js b/src/serialize.js index dba8ac0..d28b035 100644 --- a/src/serialize.js +++ b/src/serialize.js @@ -34,33 +34,34 @@ function serialize(data, options = {}) { const indentation = options.pretty ? 2 : 0 - return JSON.stringify(data, replace, indentation) + return JSON.stringify(data, replace.bind(this, options), indentation) } function createSerializationStream(data, options = {}) { const indentation = options.pretty ? 2 : 0 - const replacer = options.bigChunks ? replace : replaceAsync + const replacer = options.bigChunks ? replace.bind(this, options) : replaceAsync.bind(this, options) const stream = JSONStreamStringify(data, replacer, indentation) return stream } -function replace(key, value) { +function replace(options, key, value) { debug('key:', key) debug('value:', value) let result = value + const replaceBind = replace.bind(this, options) if (value instanceof immutable.Record) { - result = replaceRecord(value, replace) + result = replaceRecord(value, options, replaceBind) } else if (immutable.Iterable.isIterable(value)) { - result = replaceIterable(value, replace) + result = replaceIterable(value, replaceBind) } else if (Array.isArray(value)) { - result = replaceArray(value, replace) + result = replaceArray(value, replaceBind) } else if (nativeTypeHelpers.isDate(value)) { result = { '__date': value.toISOString() } @@ -69,45 +70,46 @@ function replace(key, value) { result = { '__regexp': value.toString() } } else if (typeof value === 'object' && value !== null) { - result = replacePlainObject(value, replace) + result = replacePlainObject(value, replaceBind) } debug('result:', result, '\n---') return result } -function replaceAsync(key, value) { +function replaceAsync(options, key, value) { debug('key:', key) debug('value:', value) let result = value + const replaceAsyncBind = replaceAsync.bind(this, options) if (!(value instanceof Promise)) { if (value instanceof immutable.Record) { result = new Promise((resolve) => { setImmediate(() => { - resolve(replaceRecord(value, replaceAsync)) + resolve(replaceRecord(value, options, replaceAsyncBind)) }) }) } else if (immutable.Iterable.isIterable(value)) { result = new Promise((resolve) => { setImmediate(() => { - resolve(replaceIterable(value, replaceAsync)) + resolve(replaceIterable(value, replaceAsyncBind)) }) }) } else if (Array.isArray(value)) { result = new Promise((resolve) => { setImmediate(() => { - resolve(replaceArray(value, replaceAsync)) + resolve(replaceArray(value, replaceAsyncBind)) }) }) } else if (typeof value === 'object' && value !== null) { result = new Promise((resolve) => { setImmediate(() => { - resolve(replacePlainObject(value, replaceAsync)) + resolve(replacePlainObject(value, replaceAsyncBind)) }) }) } @@ -118,7 +120,7 @@ function replaceAsync(key, value) { } -function replaceRecord(rec, replaceChild) { +function replaceRecord(rec, options, replaceChild) { debug('replaceRecord()', rec) const recordDataMap = rec.toMap() const recordData = {} @@ -127,10 +129,16 @@ function replaceRecord(rec, replaceChild) { recordData[key] = replaceChild(key, value) }) + let name = rec._name if (!rec._name) { - return recordData + + if (options.storeUnknownRecords) { + name = '__unknown' + } else { + return recordData + } } - return { "__record": rec._name, "data": recordData } + return { "__record": name, "data": recordData } } diff --git a/test/deserialize/record.js b/test/deserialize/record.js index 4293cfb..0d0a6eb 100644 --- a/test/deserialize/record.js +++ b/test/deserialize/record.js @@ -25,7 +25,6 @@ it('should deserialize a record of a known type', (test) => { }) }) - it('should not deserialize a record of an unknown type', (test) => { const data = { '__record': 'SampleRecord', @@ -43,6 +42,26 @@ it('should not deserialize a record of an unknown type', (test) => { }) +it('should deserialize a record of an unknown type', (test) => { + const data = { + '__record': 'SampleRecord', + 'data': { + 'a': 5, + 'b': 6, + }, + } + + const UnknownRecord = immutable.Record({ + 'a': 1, + 'b': 2, + }) + + helpers.testDeserialization(test, data, UnknownRecord(data['data']), { + recordTypes: {}, + parseUnknownRecords: true + }) +}) + it('should deserialize nested records of known types', (test) => { const RecordA = immutable.Record({ 'a': 1, @@ -77,3 +96,71 @@ it('should deserialize nested records of known types', (test) => { }, }) }) + + +it('should deserialize nested records of unknown and know types', (test) => { + const RecordA = immutable.Record({ + 'a': 1, + 'b': 2, + }) + const RecordB = immutable.Record({ + 'c': 3, + }, 'RecordB') + + const data = { + '__record': 'RecordA', + 'data': { + 'a': 5, + 'b': { + '__record': 'RecordB', + 'data': { + 'c': 6, + }, + }, + }, + } + + const expectedResult = RecordA({ + 'a': data['data']['a'], + 'b': RecordB(data['data']['b']['data']), + }) + + helpers.testDeserialization(test, data, expectedResult, { + recordTypes: { + 'RecordB': RecordB, + }, + parseUnknownRecords: true + }) +}) + +it('should deserialize nested records of unknown types', (test) => { + const RecordA = immutable.Record({ + 'a': 1, + 'b': 2, + }) + const RecordB = immutable.Record({ + 'c': 3, + }) + + const data = { + '__record': 'RecordA', + 'data': { + 'a': 5, + 'b': { + '__record': 'RecordB', + 'data': { + 'c': 6, + }, + }, + }, + } + + const expectedResult = RecordA({ + 'a': data['data']['a'], + 'b': RecordB(data['data']['b']['data']), + }) + + helpers.testDeserialization(test, data, expectedResult, { + parseUnknownRecords: true + }) +}) diff --git a/test/serialize/_helpers.js b/test/serialize/_helpers.js index 9ebb4df..2b5c7bf 100644 --- a/test/serialize/_helpers.js +++ b/test/serialize/_helpers.js @@ -1,12 +1,12 @@ const JsonImmutable = require('../../lib/') -exports.testSerialization = function (test, data, expectedResult) { - const result = exports.getSerializationResult(data) +exports.testSerialization = function (test, data, expectedResult, options) { + const result = exports.getSerializationResult(data, options) test.deepEqual(result, expectedResult) } -exports.getSerializationResult = function (data) { - const json = JsonImmutable.serialize(data) +exports.getSerializationResult = function (data, options) { + const json = JsonImmutable.serialize(data, options) return JSON.parse(json) } diff --git a/test/serialize/record.js b/test/serialize/record.js index 3ffbcf9..897ae82 100644 --- a/test/serialize/record.js +++ b/test/serialize/record.js @@ -47,6 +47,20 @@ it('should serialize an unnamed immutable.Record as a plain object', }) }) +it('should serialize an unnamed immutable.Record as a Record', + (test) => { + const SampleRecord = immutable.Record({ + 'a': 5, + 'b': 6, + }) + + const data = SampleRecord() + const result = helpers.getSerializationResult(data, { storeUnknownRecords: true }) + + test.is(result['__record'], '__unknown') + test.truthy(result['data']) + }) + it('should serialize a named immutable.Record data as a plain object', (test) => {