diff --git a/index.d.ts b/index.d.ts index 6713dc52..74a7d12b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -23,7 +23,7 @@ declare namespace schema { } export class Object { - constructor(definition: {[key: string]: Schema}) + constructor(definition: SchemaObject) define(definition: Schema): void } @@ -46,8 +46,11 @@ export type Schema = | SchemaObject | SchemaArray; +export type SchemaValueFunction = (t: T) => Schema; +export type SchemaValue = Schema | SchemaValueFunction; + export interface SchemaObject { - [key: string]: Schema + [key: string]: SchemaValue } export interface SchemaArray extends Array> {} diff --git a/src/__tests__/__snapshots__/index.test.js.snap b/src/__tests__/__snapshots__/index.test.js.snap index a8ceac78..fbc551cb 100644 --- a/src/__tests__/__snapshots__/index.test.js.snap +++ b/src/__tests__/__snapshots__/index.test.js.snap @@ -64,6 +64,47 @@ Object { } `; +exports[`normalize can normalize entity nested inside entity using property from parent 1`] = ` +Object { + "entities": Object { + "linkables": Object { + "1": Object { + "data": 2, + "id": 1, + "module_type": "article", + "schema_type": "media", + }, + }, + "media": Object { + "2": Object { + "id": 2, + "url": "catimage.jpg", + }, + }, + }, + "result": 1, +} +`; + +exports[`normalize can normalize entity nested inside object using property from parent 1`] = ` +Object { + "entities": Object { + "media": Object { + "2": Object { + "id": 2, + "url": "catimage.jpg", + }, + }, + }, + "result": Object { + "data": 2, + "id": 1, + "module_type": "article", + "schema_type": "media", + }, +} +`; + exports[`normalize can use fully custom entity classes 1`] = ` Object { "entities": Object { diff --git a/src/__tests__/index.test.js b/src/__tests__/index.test.js index 2fef8f3a..6437ab16 100644 --- a/src/__tests__/index.test.js +++ b/src/__tests__/index.test.js @@ -162,6 +162,59 @@ describe('normalize', () => { expect(() => normalize(test, testEntity)).not.toThrow(); }); + + test('can normalize entity nested inside entity using property from parent', () => { + const linkablesSchema = new schema.Entity('linkables'); + const mediaSchema = new schema.Entity('media'); + const listsSchema = new schema.Entity('lists'); + + const schemaMap = { + media: mediaSchema, + lists: listsSchema + }; + + linkablesSchema.define({ + data: (parent) => schemaMap[parent.schema_type] + }); + + const input = { + id: 1, + module_type: 'article', + schema_type: 'media', + data: { + id: 2, + url: 'catimage.jpg' + } + }; + + expect(normalize(input, linkablesSchema)).toMatchSnapshot(); + }); + + test('can normalize entity nested inside object using property from parent', () => { + const mediaSchema = new schema.Entity('media'); + const listsSchema = new schema.Entity('lists'); + + const schemaMap = { + media: mediaSchema, + lists: listsSchema + }; + + const linkablesSchema = { + data: (parent) => schemaMap[parent.schema_type] + }; + + const input = { + id: 1, + module_type: 'article', + schema_type: 'media', + data: { + id: 2, + url: 'catimage.jpg' + } + }; + + expect(normalize(input, linkablesSchema)).toMatchSnapshot(); + }); }); describe('denormalize', () => { diff --git a/src/schemas/Entity.js b/src/schemas/Entity.js index 559dbc13..218a2d41 100644 --- a/src/schemas/Entity.js +++ b/src/schemas/Entity.js @@ -67,7 +67,15 @@ export default class EntitySchema { Object.keys(this.schema).forEach((key) => { if (processedEntity.hasOwnProperty(key) && typeof processedEntity[key] === 'object') { const schema = this.schema[key]; - processedEntity[key] = visit(processedEntity[key], processedEntity, key, schema, addEntity, visitedEntities); + const resolvedSchema = typeof schema === 'function' ? schema(input) : schema; + processedEntity[key] = visit( + processedEntity[key], + processedEntity, + key, + resolvedSchema, + addEntity, + visitedEntities + ); } }); diff --git a/src/schemas/Object.js b/src/schemas/Object.js index 7bcae84a..d848a68d 100644 --- a/src/schemas/Object.js +++ b/src/schemas/Object.js @@ -4,7 +4,8 @@ export const normalize = (schema, input, parent, key, visit, addEntity, visitedE const object = { ...input }; Object.keys(schema).forEach((key) => { const localSchema = schema[key]; - const value = visit(input[key], input, key, localSchema, addEntity, visitedEntities); + const resolvedLocalSchema = typeof localSchema === 'function' ? localSchema(input) : localSchema; + const value = visit(input[key], input, key, resolvedLocalSchema, addEntity, visitedEntities); if (value === undefined || value === null) { delete object[key]; } else { diff --git a/typescript-tests/object.ts b/typescript-tests/object.ts index 7dce1162..c70d24d2 100644 --- a/typescript-tests/object.ts +++ b/typescript-tests/object.ts @@ -1,12 +1,22 @@ import { normalize, schema } from '../index' -const data = { - /* ...*/ -}; +type Response = { + users: Array<{ id: string }> +} +const data: Response = { users: [ { id: 'foo' } ] }; const user = new schema.Entity('users'); -const responseSchema = new schema.Object({ users: new schema.Array(user) }); -const normalizedData = normalize(data, responseSchema); +{ + const responseSchema = new schema.Object({ users: new schema.Array(user) }); + const normalizedData = normalize(data, responseSchema); +} -const responseSchemaAlt = { users: new schema.Array(user) }; -const normalizedDataAlt = normalize(data, responseSchemaAlt); +{ + const responseSchema = new schema.Object({ users: (response: Response) => new schema.Array(user) }); + const normalizedData = normalize(data, responseSchema); +} + +{ + const responseSchema = { users: new schema.Array(user) }; + const normalizedData = normalize(data, responseSchema); +}