transit-immutable-js
Advanced tools
Comparing version 0.4.0 to 0.5.0
318
index.js
var transit = require('transit-js'); | ||
var Immutable = require('immutable'); | ||
var reader = transit.reader('json', { | ||
mapBuilder: { | ||
init: function() { | ||
return {}; | ||
}, | ||
add: function(m, k, v) { | ||
m[k] = v; | ||
return m; | ||
}, | ||
finalize: function(m) { | ||
return m; | ||
} | ||
}, | ||
handlers: { | ||
iM: function(v) { | ||
var m = Immutable.Map().asMutable(); | ||
for (var i = 0; i < v.length; i += 2) { | ||
m = m.set(v[i], v[i + 1]); | ||
function recordName(record) { | ||
/* eslint no-underscore-dangle: 0 */ | ||
return record._name || record.constructor.name || 'Record'; | ||
} | ||
function createReader(recordMap) { | ||
return transit.reader('json', { | ||
mapBuilder: { | ||
init: function() { | ||
return {}; | ||
}, | ||
add: function(m, k, v) { | ||
m[k] = v; | ||
return m; | ||
}, | ||
finalize: function(m) { | ||
return m; | ||
} | ||
return m.asImmutable(); | ||
}, | ||
iOM: function(v) { | ||
var m = Immutable.OrderedMap().asMutable(); | ||
for (var i = 0; i < v.length; i += 2) { | ||
m = m.set(v[i], v[i + 1]); | ||
handlers: { | ||
iM: function(v) { | ||
var m = Immutable.Map().asMutable(); | ||
for (var i = 0; i < v.length; i += 2) { | ||
m = m.set(v[i], v[i + 1]); | ||
} | ||
return m.asImmutable(); | ||
}, | ||
iOM: function(v) { | ||
var m = Immutable.OrderedMap().asMutable(); | ||
for (var i = 0; i < v.length; i += 2) { | ||
m = m.set(v[i], v[i + 1]); | ||
} | ||
return m.asImmutable(); | ||
}, | ||
iL: function(v) { | ||
return Immutable.List(v); | ||
}, | ||
iS: function(v) { | ||
return Immutable.Set(v); | ||
}, | ||
iOS: function(v) { | ||
return Immutable.OrderedSet(v); | ||
}, | ||
iR: function(v) { | ||
var Record = recordMap[v.n]; | ||
if (!Record) { | ||
var msg = 'Tried to deserialize Record type named `' + v.n + '`, ' + | ||
'but no type with that name was passed to withRecords()'; | ||
throw new Error(msg); | ||
} | ||
return Record(v.v); | ||
} | ||
return m.asImmutable(); | ||
}, | ||
iL: function(v) { | ||
return Immutable.List(v); | ||
}, | ||
iS: function(v) { | ||
return Immutable.Set(v); | ||
}, | ||
iOS: function(v) { | ||
return Immutable.OrderedSet(v); | ||
} | ||
}); | ||
} | ||
function createWriter(recordMap, predicate) { | ||
function mapSerializer(m) { | ||
var i = 0, a = new Array(2 * m.size); | ||
if (predicate) { | ||
m = m.filter(predicate); | ||
} | ||
m.forEach(function(v, k) { | ||
a[i++] = k; | ||
a[i++] = v; | ||
}); | ||
return a; | ||
} | ||
}); | ||
var writer = createWriter(false); | ||
var handlers = transit.map([ | ||
Immutable.Map, transit.makeWriteHandler({ | ||
tag: function() { | ||
return 'iM'; | ||
}, | ||
rep: mapSerializer | ||
}), | ||
Immutable.OrderedMap, transit.makeWriteHandler({ | ||
tag: function() { | ||
return 'iOM'; | ||
}, | ||
rep: mapSerializer | ||
}), | ||
Immutable.List, transit.makeWriteHandler({ | ||
tag: function() { | ||
return "iL"; | ||
}, | ||
rep: function(v) { | ||
if (predicate) { | ||
v = v.filter(predicate); | ||
} | ||
return v.toArray(); | ||
} | ||
}), | ||
Immutable.Set, transit.makeWriteHandler({ | ||
tag: function() { | ||
return "iS"; | ||
}, | ||
rep: function(v) { | ||
if (predicate) { | ||
v = v.filter(predicate); | ||
} | ||
return v.toArray(); | ||
} | ||
}), | ||
Immutable.OrderedSet, transit.makeWriteHandler({ | ||
tag: function() { | ||
return "iOS"; | ||
}, | ||
rep: function(v) { | ||
if (predicate) { | ||
v = v.filter(predicate); | ||
} | ||
return v.toArray(); | ||
} | ||
}), | ||
Function, transit.makeWriteHandler({ | ||
tag: function() { | ||
return '_'; | ||
}, | ||
rep: function() { | ||
return null; | ||
} | ||
}), | ||
"default", transit.makeWriteHandler({ | ||
tag: function() { | ||
return 'iM'; | ||
}, | ||
rep: function(m) { | ||
if (!('toMap' in m)) { | ||
var e = "Error serializing unrecognized object " + m.toString(); | ||
throw new Error(e); | ||
} | ||
return mapSerializer(m.toMap()); | ||
} | ||
}), | ||
]); | ||
exports.toJSON = toJSON; | ||
function toJSON(data) { | ||
return writer.write(data); | ||
Object.keys(recordMap).forEach(function(name) { | ||
handlers.set(recordMap[name], makeRecordHandler(name, predicate)); | ||
}); | ||
return transit.writer('json', { | ||
handlers: handlers | ||
}); | ||
} | ||
exports.fromJSON = fromJSON; | ||
function fromJSON(data) { | ||
return reader.read(data); | ||
function makeRecordHandler(name) { | ||
return transit.makeWriteHandler({ | ||
tag: function() { | ||
return 'iR'; | ||
}, | ||
rep: function(m) { | ||
return { | ||
n: name, | ||
v: m.toObject() | ||
}; | ||
} | ||
}); | ||
} | ||
function withFilter(predicate) { | ||
var filteredWriter = createWriter(predicate); | ||
function buildRecordMap(recordClasses) { | ||
var recordMap = {}; | ||
recordClasses.forEach(function(RecordType) { | ||
var rec = new RecordType({}); | ||
var recName = recordName(rec); | ||
if (!recName || recName === 'Record') { | ||
throw new Error('Cannot (de)serialize Record() without a name'); | ||
} | ||
if (recordMap[recName]) { | ||
throw new Error('There\'s already a constructor for a Record named ' + | ||
recName); | ||
} | ||
recordMap[recName] = RecordType; | ||
}); | ||
return recordMap; | ||
} | ||
function createInstance(options) { | ||
var records = options.records || {}; | ||
var filter = options.filter || false; | ||
var reader = createReader(records); | ||
var writer = createWriter(records, filter); | ||
return { | ||
toJSON: function(data) { | ||
return filteredWriter.write(data); | ||
toJSON: function toJSON(data) { | ||
return writer.write(data); | ||
}, | ||
fromJSON: fromJSON | ||
fromJSON: function fromJSON(json) { | ||
return reader.read(json); | ||
}, | ||
withFilter: function(predicate) { | ||
return createInstance({ records: records, filter: predicate }); | ||
}, | ||
withRecords: function(recordClasses) { | ||
var recordMap = buildRecordMap(recordClasses); | ||
return createInstance({ records: recordMap, filter: filter }); | ||
} | ||
}; | ||
} | ||
exports.withFilter = withFilter; | ||
function createWriter(predicate) { | ||
return transit.writer('json', { | ||
handlers: transit.map([ | ||
Immutable.Map, transit.makeWriteHandler({ | ||
tag: function() { | ||
return 'iM'; | ||
}, | ||
rep: function(m) { | ||
var i = 0, a = new Array(2 * m.size); | ||
if (predicate) { | ||
m = m.filter(predicate); | ||
} | ||
m.forEach(function(v, k) { | ||
a[i++] = k; | ||
a[i++] = v; | ||
}); | ||
return a; | ||
} | ||
}), | ||
Immutable.OrderedMap, transit.makeWriteHandler({ | ||
tag: function() { | ||
return 'iOM'; | ||
}, | ||
rep: function(m) { | ||
var i = 0, a = new Array(2 * m.size); | ||
if (predicate) { | ||
m = m.filter(predicate); | ||
} | ||
m.forEach(function(v, k) { | ||
a[i++] = k; | ||
a[i++] = v; | ||
}); | ||
return a; | ||
} | ||
}), | ||
Immutable.List, transit.makeWriteHandler({ | ||
tag: function() { | ||
return "iL"; | ||
}, | ||
rep: function(v) { | ||
if (predicate) { | ||
v = v.filter(predicate); | ||
} | ||
return v.toArray(); | ||
} | ||
}), | ||
Immutable.Set, transit.makeWriteHandler({ | ||
tag: function() { | ||
return "iS"; | ||
}, | ||
rep: function(v) { | ||
if (predicate) { | ||
v = v.filter(predicate); | ||
} | ||
return v.toArray(); | ||
} | ||
}), | ||
Immutable.OrderedSet, transit.makeWriteHandler({ | ||
tag: function() { | ||
return "iOS"; | ||
}, | ||
rep: function(v) { | ||
if (predicate) { | ||
v = v.filter(predicate); | ||
} | ||
return v.toArray(); | ||
} | ||
}), | ||
Function, transit.makeWriteHandler({ | ||
tag: function() { | ||
return '_'; | ||
}, | ||
rep: function() { | ||
return null; | ||
} | ||
}) | ||
]) | ||
}); | ||
} | ||
module.exports = createInstance({}); |
{ | ||
"name": "transit-immutable-js", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "Transit serialisation for Immutable.js", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -53,12 +53,32 @@ # transit-immutable-js | ||
### `transit.fromJSON(object) => string` | ||
### `transit.toJSON(object) => string` | ||
Convert an immutable object into a JSON representation | ||
### `transit.toJSON(string) => object` | ||
### `transit.fromJSON(string) => object` | ||
Convert a JSON representation back into an immutable object | ||
> The `withXXX` methods can be combined as desired. | ||
### `transit.withFilter(function) => transit` | ||
Create a modified version of the transit API that deeply applies the provided filter function to all immutable collections before serialising. Can be used to exclude entries. | ||
### `transit.withRecords(Array recordClasses) => transit` | ||
Creates a modified version of the transit API with support for serializing/deserializing [Record](https://facebook.github.io/immutable-js/docs/#/) objects. If a Record is included in an object to be serialized without the proper handler, on encoding it will be encoded as an `Immutable.Map`. | ||
## Example `Record` Usage: | ||
```js | ||
var FooRecord = Immutable.Record({ | ||
a: 1, | ||
b: 2, | ||
}, 'foo'); | ||
var data = new FooRecord(); | ||
var recordTransit = transit.withRecords([FooRecord]); | ||
var encodedJSON = transit.toJSON(data); | ||
``` |
139
test/test.js
@@ -38,3 +38,2 @@ /* eslint-env mocha */ | ||
.set(1, 'c') | ||
}), | ||
@@ -107,6 +106,107 @@ | ||
}); | ||
describe('Records', function() { | ||
var FooRecord = Immutable.Record({ | ||
a: 1, | ||
b: 2, | ||
}, 'foo'); | ||
var BarRecord = Immutable.Record({ | ||
c: '1', | ||
d: '2' | ||
}, 'bar'); | ||
var NamelessRecord = Immutable.Record({}); | ||
var recordTransit = transit.withRecords([FooRecord, BarRecord]); | ||
it('should round-trip simple records', function() { | ||
var data = Immutable.Map({ | ||
myFoo: new FooRecord(), | ||
myBar: new BarRecord() | ||
}); | ||
var roundTrip = recordTransit.fromJSON(recordTransit.toJSON(data)); | ||
expect(roundTrip).to.eql(data); | ||
expect(roundTrip.get('myFoo').a).to.eql(1); | ||
expect(roundTrip.get('myFoo').b).to.eql(2); | ||
expect(roundTrip.get('myBar').c).to.eql('1'); | ||
expect(roundTrip.get('myBar').d).to.eql('2'); | ||
}); | ||
it('should round-trip complex nested records', function() { | ||
var data = Immutable.Map({ | ||
foo: new FooRecord({ | ||
b: Immutable.List.of(BarRecord(), BarRecord({c: 22})) | ||
}), | ||
bar: new BarRecord() | ||
}); | ||
var roundTrip = recordTransit.fromJSON(recordTransit.toJSON(data)); | ||
expect(roundTrip).to.eql(data); | ||
}); | ||
it('should serialize unspecified Record as a Map', function() { | ||
var data = Immutable.Map({ | ||
myFoo: new FooRecord(), | ||
myBar: new BarRecord() | ||
}); | ||
var oneRecordTransit = transit.withRecords([FooRecord]); | ||
var roundTripOneRecord = oneRecordTransit.fromJSON( | ||
oneRecordTransit.toJSON(data)); | ||
expect(roundTripOneRecord).to.eql( | ||
Immutable.fromJS({ | ||
myFoo: new FooRecord(), | ||
myBar: {c: '1', d: '2'} | ||
}) | ||
); | ||
var roundTripWithoutRecords = transit.fromJSON(transit.toJSON(data)); | ||
expect(roundTripWithoutRecords).to.eql( | ||
Immutable.fromJS({ | ||
myFoo: {a: 1, b: 2}, | ||
myBar: {c: '1', d: '2'} | ||
}) | ||
); | ||
}); | ||
it('throws an error when it is passed a record with no name', function() { | ||
expect(function() { | ||
transit.withRecords([NamelessRecord]); | ||
}).to.throw(); | ||
}); | ||
it('throws an error when it reads an unknown record type', function() { | ||
var input = new FooRecord(); | ||
var json = recordTransit.toJSON(input); | ||
var emptyRecordTransit = transit.withRecords([]); | ||
expect(function() { | ||
emptyRecordTransit.fromJSON(json); | ||
}).to.throw(); | ||
}); | ||
it('throws an error if two records have the same name', function() { | ||
var R1 = Immutable.Record({}, 'R1'); | ||
var R1_2 = Immutable.Record({}, 'R1'); | ||
expect(function() { | ||
transit.withRecords([R1, R1_2]); | ||
}).to.throw(); | ||
}); | ||
}); | ||
describe('.withFilter(predicate)', function(){ | ||
var filter = transit.withFilter(function(val, key) { | ||
var filterFunction = function(val, key) { | ||
return key[0] !== '_'; | ||
}); | ||
}; | ||
var filter = transit.withFilter(filterFunction); | ||
it('can ignore Map entries', function() { | ||
@@ -135,3 +235,3 @@ var input = Immutable.Map({ | ||
it('can ignore Set entries', function() { | ||
var input = Immutable.Set.of(1, 2, 3, 3, 'a'); | ||
var input = Immutable.OrderedSet.of(1, 2, 3, 3, 'a'); | ||
filter = transit.withFilter(function(val) { | ||
@@ -158,3 +258,34 @@ return typeof val === 'number'; | ||
}); | ||
it('can ignore Maps nested in Records', function() { | ||
var MyRecord = Immutable.Record({ | ||
a: null, | ||
_b: 'bar' | ||
}, 'myRecord'); | ||
var input = new MyRecord({a: Immutable.Map({_c: 1, d: 2}), _b: 'baz' }); | ||
var recordFilter = transit | ||
.withRecords([MyRecord]) | ||
.withFilter(filterFunction); | ||
var result = recordFilter.fromJSON(recordFilter.toJSON(input)); | ||
expect(result.getIn(['a', 'd'])).to.eql(2); | ||
expect(result.getIn(['a', '_c'])).to.eql(undefined); | ||
expect(result.get('_b')).to.eql('baz'); | ||
}); | ||
}); | ||
describe('Unknown Input', function() { | ||
it('fails when an unrecognized object is passed', function() { | ||
var MyObject = function() {}; | ||
var MyObjectInstance = new MyObject(); | ||
expect(function() { | ||
transit.toJSON(MyObjectInstance); | ||
}).to.throw(); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
19007
421
84