From 7d527b64c6d5f19486ce9d4c2225b56061e36744 Mon Sep 17 00:00:00 2001 From: Tim Hwang Date: Fri, 23 Sep 2016 13:39:01 -0400 Subject: [PATCH] Can normalize entities keyed with their ID --- README.md | 6 ++++++ src/EntitySchema.js | 4 ++-- src/index.js | 34 +++++++++++++++++++++------------- test/index.js | 35 ++++++++++++++++++++++++++++++++++- 4 files changed, 63 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 1bcaaf15..fb64dbfc 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,12 @@ const article = new Schema('articles', { idAttribute: 'slug' }); function generateSlug(entity) { /* ... */ } const article = new Schema('articles', { idAttribute: generateSlug }); +// The function is passed both the current value and its key +// This is helpful in occasions where your data is keyed by id, but doesn't contain id inside the value +const keyedByIdJson = { 1: { ... }, 2: { ... }, 3: { ... } }; +function generateSlug(entity, id) { return id; } +const article = new Schema('articles', { idAttribute: generateSlug }); + // You can also specify meta properties to be used for customizing the output in assignEntity (see below) const article = new Schema('articles', { idAttribute: 'slug', meta: { removeProps: ['publisher'] }}); diff --git a/src/EntitySchema.js b/src/EntitySchema.js index 02d0836b..9c1e4de7 100644 --- a/src/EntitySchema.js +++ b/src/EntitySchema.js @@ -22,8 +22,8 @@ export default class EntitySchema { return this._key; } - getId(entity) { - return this._getId(entity); + getId(entity, key) { + return this._getId(entity, key); } getIdAttribute() { diff --git a/src/index.js b/src/index.js index f525297c..b52047a5 100644 --- a/src/index.js +++ b/src/index.js @@ -8,7 +8,7 @@ function defaultAssignEntity(normalized, key, entity) { normalized[key] = entity; } -function visitObject(obj, schema, bag, options) { +function visitObject(obj, schema, bag, options, collectionKey) { const { assignEntity = defaultAssignEntity } = options; const defaults = schema && schema.getDefaults && schema.getDefaults(); @@ -17,7 +17,7 @@ function visitObject(obj, schema, bag, options) { for (let key in obj) { if (obj.hasOwnProperty(key)) { const resolvedSchema = typeof schema[key] === 'function' ? schema[key].call(null, obj) : schema[key]; - const entity = visit(obj[key], resolvedSchema, bag, options); + const entity = visit(obj[key], resolvedSchema, bag, options, collectionKey); assignEntity.call(null, normalized, key, entity, obj, schema); if (schemaAssignEntity) { schemaAssignEntity.call(null, normalized, key, entity, obj, schema); @@ -28,13 +28,13 @@ function visitObject(obj, schema, bag, options) { } function defaultMapper(iterableSchema, itemSchema, bag, options) { - return (obj) => visit(obj, itemSchema, bag, options); + return (obj, key) => visit(obj, itemSchema, bag, options, key); } function polymorphicMapper(iterableSchema, itemSchema, bag, options) { - return (obj) => { + return (obj, key) => { const schemaKey = iterableSchema.getSchemaKey(obj); - const result = visit(obj, itemSchema[schemaKey], bag, options); + const result = visit(obj, itemSchema[schemaKey], bag, options, key); return { id: result, schema: schemaKey }; }; } @@ -47,7 +47,7 @@ function visitIterable(obj, iterableSchema, bag, options) { return obj.map(curriedItemMapper); } else { return Object.keys(obj).reduce(function (objMap, key) { - objMap[key] = curriedItemMapper(obj[key]); + objMap[key] = curriedItemMapper(obj[key], key); return objMap; }, {}); } @@ -76,11 +76,11 @@ function defaultMergeIntoEntity(entityA, entityB, entityKey) { } } -function visitEntity(entity, entitySchema, bag, options) { +function visitEntity(entity, entitySchema, bag, options, collectionKey) { const { mergeIntoEntity = defaultMergeIntoEntity } = options; const entityKey = entitySchema.getKey(); - const id = entitySchema.getId(entity); + const id = entitySchema.getId(entity, collectionKey); if (!bag.hasOwnProperty(entityKey)) { bag[entityKey] = {}; @@ -91,28 +91,35 @@ function visitEntity(entity, entitySchema, bag, options) { } let stored = bag[entityKey][id]; - let normalized = visitObject(entity, entitySchema, bag, options); + let normalized = visitObject(entity, entitySchema, bag, options, collectionKey); mergeIntoEntity(stored, normalized, entityKey); return id; } -function visit(obj, schema, bag, options) { +function visit(obj, schema, bag, options, collectionKey) { if (!isObject(obj) || !isObject(schema)) { return obj; } if (schema instanceof EntitySchema) { - return visitEntity(obj, schema, bag, options); + return visitEntity(obj, schema, bag, options, collectionKey); } else if (schema instanceof IterableSchema) { return visitIterable(obj, schema, bag, options); } else if (schema instanceof UnionSchema) { return visitUnion(obj, schema, bag, options); } else { - return visitObject(obj, schema, bag, options); + return visitObject(obj, schema, bag, options, collectionKey); } } +function normalizeResult(result) { + if (isObject(result) && isEqual(Object.keys(result), Object.keys(result).map(key => result[key]))) { + return Object.keys(result); + } + return result; +} + export function arrayOf(schema, options) { return new IterableSchema(schema, options); } @@ -139,8 +146,9 @@ export function normalize(obj, schema, options = {}) { let bag = {}; let result = visit(obj, schema, bag, options); + return { entities: bag, - result + result: normalizeResult(result) }; } diff --git a/test/index.js b/test/index.js index 49796cbc..e8533a55 100644 --- a/test/index.js +++ b/test/index.js @@ -1950,4 +1950,37 @@ describe('normalizr', function () { }).should.throw(); }); -}); + it('can normalize iterables keyed with their id', function () { + var user = new Schema('users', { + idAttribute: function(obj, key) { + return key; + } + }) + + var input = { + 1: { + name: 'Adam' + }, + 4: { + name: 'Jeremy' + } + }; + + Object.freeze(input); + + normalize(input, valuesOf(user)).should.eql({ + result: ['1', '4'], + entities: { + users: { + '1': { + name: 'Adam' + }, + '4': { + name: 'Jeremy' + } + } + } + }); + }); + +}); \ No newline at end of file