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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const data = {

// Serialization
const json = serialize(data)
// json == '{"x":{"__iterable":"Map","data":[["y",{"__iterable":"List","data":[1,2,3]"}]]}}'
// json == '{"x":{"__collection":"Map","data":[["y",{"__collection":"List","data":[1,2,3]"}]]}}'

// Deserialize
const result = deserialize(json)
Expand Down Expand Up @@ -108,6 +108,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.
- `throwOnMissingRecordType=true`: Creates an `AnonymousRecord` for missing RecordTypes with all decoded data keys.

Return value:

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"build": "tsc",
"lint-fix": "tslint -c ./tslint.json --project ./tsconfig.json --fix",
"lint": "tslint -c ./tslint.json --project ./tsconfig.json",
"prettier": "prettier \"src/**/*.{js,jsx,ts,tsx}\" --write",
"prettier": "prettier \"{src,test}/**/*.{js,jsx,ts,tsx}\" --write",
"test": "npm run build && ava",
"typecheck": "tsc --noEmit --listFiles false"
},
Expand All @@ -24,7 +24,7 @@
}
},
"peerDependencies": {
"immutable": "3.x.x"
"immutable": "^4.0.0-rc.12"
},
"dependencies": {
"@types/node": "^11.10.0",
Expand All @@ -33,7 +33,7 @@
"devDependencies": {
"ava": "^1.2.1",
"husky": "^1.3.1",
"immutable": "3.8.x",
"immutable": "^4.0.0-rc.12",
"lint-staged": "^8.1.5",
"prettier": "^1.16.4",
"tslint": "^5.13.1",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ export * from './serialize';

export * from './types/options';
export * from './types/serializedData';
export * from './types/iterableTypes';
export * from './types/collectionTypes';
6 changes: 3 additions & 3 deletions src/serialize.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Iterable, Record } from 'immutable';
import { isCollection, Record } from 'immutable';
import JSONStreamStringify from 'json-stream-stringify';
import {
SerializationOptions,
Expand All @@ -15,8 +15,8 @@ export function serialize(
options: SerializationOptions = {},
): string | never {
if (
Iterable.isIterable(data) ||
data instanceof Record ||
isCollection(data) ||
Record.isRecord(data) ||
isSupportedNativeType(data)
) {
const patchedData = Object.create(data);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type IterableType =
export type CollectionType =
| 'List'
| 'Map'
| 'OrderedMap'
Expand Down
3 changes: 2 additions & 1 deletion src/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export interface SerializationStreamOptions {
}

export interface DeserializationOptions {
recordTypes?: { [recordName: string]: Record.Class };
recordTypes?: { [recordName: string]: ReturnType<typeof Record> };
throwOnMissingRecordType?: boolean;
}
6 changes: 3 additions & 3 deletions src/types/serializedData.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IterableType } from './iterableTypes';
import { CollectionType } from './collectionTypes';

export interface SerializedIterable {
__iterable: IterableType;
export interface SerializedCollection {
__collection: CollectionType;
data: any[] | Array<[string | number, any]>;
}

Expand Down
10 changes: 5 additions & 5 deletions src/utils/iterables.ts → src/utils/collections.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {
Iterable,
Collection,
List,
Map,
OrderedMap,
OrderedSet,
Set,
Stack,
} from 'immutable';
import { IterableType } from '../types/iterableTypes';
import { CollectionType } from '../types/collectionTypes';

export function getIterableType(
iterable: Iterable<string | number, any>,
): IterableType | undefined {
export function getCollectionType(
iterable: Collection<string | number, any>,
): CollectionType | undefined {
if (List.isList(iterable)) {
return 'List';
}
Expand Down
100 changes: 61 additions & 39 deletions src/utils/decoders.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import { List, Map, OrderedMap, OrderedSet, Set, Stack } from 'immutable';
import {
Collection,
List,
Map,
OrderedMap,
OrderedSet,
Record,
Set,
Stack,
} from 'immutable';
import { DeserializationOptions } from '../types/options';
import { SerializedIterable, SerializedRecord } from '../types/serializedData';
import {
SerializedCollection,
SerializedRecord,
} from '../types/serializedData';
import { getUniqueId } from './getUniqueId';

export function decodeData(
key: string,
Expand All @@ -10,8 +23,8 @@ export function decodeData(
if (typeof value === 'object' && value) {
if (value.__record) {
return decodeRecord(key, value, options);
} else if (value.__iterable) {
return decodeIterable(key, value, options);
} else if (value.__collection) {
return decodeCollection(key, value, options);
} else if (value.__date) {
return new Date(value.__date);
} else if (value.__regexp) {
Expand All @@ -22,59 +35,68 @@ export function decodeData(
return value;
}

const getUniqueAnonymousRecordId = getUniqueId('AnonymousRecord');

function decodeRecord(
key: string,
recInfo: SerializedRecord,
options: DeserializationOptions,
) {
const { __record: recordName, data } = recInfo;
const { recordTypes = {} } = options;
const { recordTypes = {}, throwOnMissingRecordType = true } = options;
const RecordType = recordTypes[recordName];
if (!RecordType) {
if (!RecordType && throwOnMissingRecordType) {
throw new Error(`Unknown record type: ${recordName}`);
}

let decodedData: any = decodeData(key, data, options);
if (typeof (RecordType as any).migrate === 'function') {
if (!!RecordType && typeof (RecordType as any).migrate === 'function') {
decodedData = (RecordType as any).migrate(decodedData);
}

return new RecordType(decodedData);
let record;
if (!RecordType) {
// If the record type does not exist, create an AnonymousRecord
// that contains all of the keys in the decodedData with a default of
// undefined
const defaultsForAnonymousRecord = Object.keys(decodedData).reduce(
(defaults, dataKey: string) => {
defaults[dataKey] = undefined;
return defaults;
},
{} as any,
);
record = new (Record(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be kinda cool if you cache this Record type somehow based on the keys so there is only one anon. Record type for a given "schema". Idk how much overhead it would be in code / what the perf tradeoffs would be between the two.

defaultsForAnonymousRecord,
getUniqueAnonymousRecordId(),
))(decodedData);
} else {
record = new RecordType(decodedData);
}

return record;
}

function decodeIterable(
const collectionTypeToConstructor = {
List,
Map,
OrderedMap,
OrderedSet,
Set,
Stack,
};

function decodeCollection(
key: string,
iterInfo: SerializedIterable,
iterInfo: SerializedCollection,
options: DeserializationOptions,
):
| Map<any, any>
| Set<any>
| List<any>
| OrderedSet<any>
| Stack<any>
| OrderedMap<any, any>
| never {
const { __iterable: iterableType, data } = iterInfo;
switch (iterableType) {
case 'List':
return List(decodeData(key, data, options));
): Collection<any, any> | never {
const { __collection: collectionType, data } = iterInfo;
const CollectionConstructor = collectionTypeToConstructor[collectionType];

case 'Set':
return Set(decodeData(key, data, options));

case 'OrderedSet':
return OrderedSet(decodeData(key, data, options));

case 'Stack':
return Stack(decodeData(key, data, options));

case 'Map':
return Map(decodeData(key, data, options));

case 'OrderedMap':
return OrderedMap(decodeData(key, data, options));

default:
throw new Error(`Unknown iterable type: ${iterableType}`);
if (!CollectionConstructor) {
throw new Error(`Unknown collection type: ${collectionType}`);
}

return (CollectionConstructor as any)(decodeData(key, data, options));
}
4 changes: 4 additions & 0 deletions src/utils/getUniqueId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export function getUniqueId(prefix?: string): () => string {
let idCounter = 0;
return () => `${prefix ? `${prefix}-` : ''}${idCounter++}`;
}
Loading