Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

normalizr

Package Overview
Dependencies
Maintainers
2
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

normalizr - npm Package Compare versions

Comparing version 2.3.1 to 3.0.0-beta

CHANGELOG.md

67

package.json
{
"name": "normalizr",
"version": "2.3.1",
"version": "3.0.0-beta",
"description": "Normalizes JSON according to schema for Redux and Flux applications",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"bugs": {
"url": "https://github.com/paularmstrong/normalizr/issues"
},
"homepage": "https://github.com/paularmstrong/normalizr",
"repository": {

@@ -19,39 +21,42 @@ "url": "https://github.com/paularmstrong/normalizr.git",

"files": [
"dist",
"lib",
"src"
"dist/",
"LICENSE",
"README.md"
],
"author": "Dan Abramov",
"main": "dist/index.js",
"scripts": {
"build": "npm run clean && mkdirp dist && npm-run-all --parallel build:development build:production",
"build:development": "NODE_ENV=development rollup -c",
"build:production": "NODE_ENV=production rollup -c",
"clean": "rimraf dist",
"flow": "flow src; test $? -eq 0 -o $? -eq 2",
"lint": "eslint ./ --fix",
"prebuild": "npm run clean",
"prepublish": "npm run build",
"test": "jest",
"test:coverage": "npm run test -- --coverage && cat ./coverage/lcov.info | coveralls"
},
"author": "Paul Armstrong",
"contributors": [
"Paul Armstrong"
"Dan Abramov"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/paularmstrong/normalizr/issues"
},
"homepage": "https://github.com/paularmstrong/normalizr",
"scripts": {
"test": "jest",
"test:watch": "npm run test -- --watch",
"prebuild": "rimraf dist lib",
"build": "webpack && babel src --ignore __tests__ --out-dir lib",
"prepublish": "npm run build"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-core": "^6.18.2",
"babel-loader": "^6.2.7",
"babel-eslint": "^7.1.1",
"babel-preset-es2015": "^6.18.0",
"babel-preset-es2015-rollup": "^3.0.0",
"babel-preset-stage-1": "^6.16.0",
"babel-register": "^6.18.0",
"copy-webpack-plugin": "^4.0.1",
"jest": "^17.0.3",
"rimraf": "^2.4.2",
"typescript": "1.8.10",
"typescript-definition-tester": "^0.0.5",
"webpack": "^1.13.3"
},
"dependencies": {
"lodash": "^4.17.2"
"coveralls": "^2.11.15",
"eslint": "^3.12.2",
"flow-bin": "^0.37.1",
"jest": "^18.0.0",
"mkdirp": "^0.5.1",
"npm-run-all": "^3.1.2",
"rimraf": "^2.5.4",
"rollup": "^0.37.0",
"rollup-plugin-babel": "^2.7.1",
"rollup-plugin-filesize": "^1.0.1",
"rollup-plugin-uglify": "^1.0.1"
}
}

@@ -1,564 +0,91 @@

# normalizr [![build status](https://img.shields.io/travis/paularmstrong/normalizr/master.svg?style=flat-square)](https://travis-ci.org/paularmstrong/normalizr) [![npm version](https://img.shields.io/npm/v/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/normalizr) [![npm downloads](https://img.shields.io/npm/dm/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/normalizr)
# normalizr [![build status](https://img.shields.io/travis/paularmstrong/normalizr/normalizr3.svg?style=flat-square)](https://travis-ci.org/paularmstrong/normalizr) [![Coverage Status](https://img.shields.io/coveralls/paularmstrong/normalizr/normalizr3.svg?style=flat-square)](https://coveralls.io/github/paularmstrong/normalizr?branch=normalizr3) [![npm version](https://img.shields.io/npm/v/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/normalizr) [![npm downloads](https://img.shields.io/npm/dm/normalizr.svg?style=flat-square)](https://www.npmjs.com/package/normalizr)
Normalizes deeply nested JSON API responses according to a schema for [Flux](https://facebook.github.io/flux) and [Redux](https://github.com/reactjs/redux) apps.
Kudos to Jing Chen for suggesting this approach.
## Motivation
## Installation
Many APIs, public or not, return JSON data that has deeply nested objects. Using data in this kind of structure is often [very difficult](https://groups.google.com/forum/#!topic/reactjs/jbh50-GJxpg) for JavaScript applications, especially those using [Flux](http://facebook.github.io/flux/) or [Redux](http://redux.js.org/).
```
npm install --save normalizr
```
## Solution
## Sample App
Normalizr is a small, but powerful utility for taking JSON with a schema definition and returning nested entities with their IDs, gathered in dictionaries.
### Flux
## Documentation
See **[flux-react-router-example](https://github.com/gaearon/flux-react-router-example)**.
* [Introduction](/docs/introduction.md)
* [Quick Start](/docs/quickstart.md)
* [API](/docs/api.md)
- [normalize](/docs/api.md#normalize)
- [schema](/docs/api.md#schema)
### Redux
## Examples
See **[redux/examples/real-world](https://github.com/reactjs/redux/tree/master/examples/real-world)**.
* [Normalizing GitHub Issues](/examples/github)
* [Relational Data](/examples/relationships)
* [Redux](/examples/redux)
## The Problem
## Quick Start
* You have a JSON API that returns deeply nested objects;
* You want to port your app to [Flux](https://github.com/facebook/flux) or [Redux](https://github.com/reactjs/redux);
* You noticed [it's hard](https://groups.google.com/forum/#!topic/reactjs/jbh50-GJxpg) for Stores (or Reducers) to consume data from nested API responses.
Consider a typical blog post. The API response for a single post might look something like this:
Normalizr takes JSON and a schema and **replaces nested entities with their IDs, gathering all entities in dictionaries**.
For example,
```javascript
[{
id: 1,
title: 'Some Article',
author: {
id: 1,
name: 'Dan'
}
}, {
id: 2,
title: 'Other Article',
author: {
id: 1,
name: 'Dan'
}
}]
```
can be normalized to
```javascript
```json
{
result: [1, 2],
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"commenter": {
"id": "2",
"name": "Nicole"
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
]
}
```
Note the flat structure (all nesting is gone).
We have two nested entity types within our `article`: `users` and `comments`. Using various `schema`, we can normalize all three entity types down:
## Features
```js
import { normalize, schema } from 'normalizr';
* Entities can be nested inside other entities, objects and arrays;
* Combine entity schemas to express any kind of API response;
* Entities with same IDs are automatically merged (with a warning if they differ);
* Allows using a custom ID attribute (e.g. slug).
// Define a users schema
const user = new schema.Entity('users');
## Usage
```javascript
import { normalize, Schema, arrayOf } from 'normalizr';
```
First, define a schema for our entities:
```javascript
const article = new Schema('articles');
const user = new Schema('users');
```
Then we define nesting rules:
```javascript
article.define({
author: user,
contributors: arrayOf(user)
// Define your comments schema
const comment = new schema.Entity('comments', {
commenter: user
});
```
Now we can use this schema in our API response handlers:
```javascript
const ServerActionCreators = {
// These are two different XHR endpoints with different response schemas.
// We can use the schema objects defined earlier to express both of them:
receiveOneArticle(response) {
// Here, the response is an object containing data about one article.
// Passing the article schema as second parameter to normalize() lets it
// correctly traverse the response tree and gather all entities:
// BEFORE:
// {
// id: 1,
// title: 'Some Article',
// author: {
// id: 7,
// name: 'Dan'
// },
// contributors: [{
// id: 10,
// name: 'Abe'
// }, {
// id: 15,
// name: 'Fred'
// }]
// }
//
// AFTER:
// {
// result: 1, // <--- Note object is referenced by ID
// entities: {
// articles: {
// 1: {
// author: 7, // <--- Same happens for references to
// contributors: [10, 15] // <--- other entities in the schema
// ...}
// },
// users: {
// 7: { ... },
// 10: { ... },
// 15: { ... }
// }
// }
// }
response = normalize(response, article);
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVE_ONE_ARTICLE,
response
});
},
receiveAllArticles(response) {
// Here, the response is an object with the key 'articles' referencing
// an array of article objects. Passing { articles: arrayOf(article) } as
// second parameter to normalize() lets it correctly traverse the response
// tree and gather all entities:
// BEFORE:
// {
// articles: [{
// id: 1,
// title: 'Some Article',
// author: {
// id: 7,
// name: 'Dan'
// },
// ...
// },
// ...
// ]
// }
//
// AFTER:
// {
// result: {
// articles: [1, 2, ...] // <--- Note how object array turned into ID array
// },
// entities: {
// articles: {
// 1: { author: 7, ... }, // <--- Same happens for references to other entities in the schema
// 2: { ... },
// ...
// },
// users: {
// 7: { ... },
// ..
// }
// }
// }
response = normalize(response, {
articles: arrayOf(article)
});
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVE_ALL_ARTICLES,
response
});
}
}
```
Finally, different Stores can tune in to listen to all API responses and grab entity lists from `action.response.entities`:
```javascript
AppDispatcher.register((payload) => {
const { action } = payload;
if (action.response && action.response.entities && action.response.entities.users) {
mergeUsers(action.response.entities.users);
UserStore.emitChange();
break;
}
});
```
## API Reference
### `new Schema(key, [options])`
Schema lets you define a type of entity returned by your API.
This should correspond to model in your server code.
The `key` parameter lets you specify the name of the dictionary for this kind of entity.
```javascript
const article = new Schema('articles');
// You can use a custom id attribute
const article = new Schema('articles', { idAttribute: 'slug' });
// Or you can specify a function to infer it
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'] }});
// You can specify custom `assignEntity` function to be run after the `assignEntity` function passed to `normalize`
const article = new Schema('articles', { assignEntity: function (output, key, value, input) {
if (key === 'id_str') {
output.id = value;
if ('id_str' in output) {
delete output.id_str;
}
} else {
output[key] = value;
}
}})
// You can specify default values for the entity
const article = new Schema('articles', { defaults: { likes: 0 } });
```
### `Schema.prototype.define(nestedSchema)`
Lets you specify relationships between different entities.
```javascript
const article = new Schema('articles');
const user = new Schema('users');
article.define({
author: user
});
```
### `Schema.prototype.getKey()`
Returns the key of the schema.
```javascript
const article = new Schema('articles');
article.getKey();
// articles
```
### `Schema.prototype.getIdAttribute()`
Returns the idAttribute of the schema.
```javascript
const article = new Schema('articles');
const slugArticle = new Schema('articles', { idAttribute: 'slug' });
article.getIdAttribute();
// id
slugArticle.getIdAttribute();
// slug
```
### `Schema.prototype.getDefaults()`
Returns the default values of the schema.
```javascript
const article = new Schema('articles', { defaults: { likes: 0 } });
article.getDefaults();
// { likes: 0 }
```
### `arrayOf(schema, [options])`
Describes an array of the schema passed as argument.
```javascript
const article = new Schema('articles');
const user = new Schema('users');
article.define({
// Define your article
const article = new schema.Entity('articles', {
author: user,
contributors: arrayOf(user)
comments: [ comment ]
});
```
If the array contains entities with different schemas, you can use the `schemaAttribute` option to specify which schema to use for each entity:
```javascript
const article = new Schema('articles');
const image = new Schema('images');
const video = new Schema('videos');
const asset = {
images: image,
videos: video
};
// You can specify the name of the attribute that determines the schema
article.define({
assets: arrayOf(asset, { schemaAttribute: 'type' })
});
// Or you can specify a function to infer it
function inferSchema(entity) { /* ... */ }
article.define({
assets: arrayOf(asset, { schemaAttribute: inferSchema })
});
const normalizedData = normalize(originalData, article);
```
### `valuesOf(schema, [options])`
Now, `normalizedData` will be:
Describes a map whose values follow the schema passed as argument.
```javascript
const article = new Schema('articles');
const user = new Schema('users');
article.define({
collaboratorsByRole: valuesOf(user)
});
```
If the map contains entities with different schemas, you can use the `schemaAttribute` option to specify which schema to use for each entity:
```javascript
const article = new Schema('articles');
const user = new Schema('users');
const group = new Schema('groups');
const collaborator = {
users: user,
groups: group
};
// You can specify the name of the attribute that determines the schema
article.define({
collaboratorsByRole: valuesOf(collaborator, { schemaAttribute: 'type' })
});
// Or you can specify a function to infer it
function inferSchema(entity) { /* ... */ }
article.define({
collaboratorsByRole: valuesOf(collaborator, { schemaAttribute: inferSchema })
});
```
### `unionOf(schemaMap, [options])`
Describe a schema which is a union of multiple schemas. This is useful if you need the polymorphic behavior provided by `arrayOf` or `valuesOf` but for non-collection fields.
Use the required `schemaAttribute` option to specify which schema to use for each entity.
```javascript
const group = new Schema('groups');
const user = new Schema('users');
// a member can be either a user or a group
const member = {
users: user,
groups: group
};
// You can specify the name of the attribute that determines the schema
group.define({
owner: unionOf(member, { schemaAttribute: 'type' })
});
// Or you can specify a function to infer it
function inferSchema(entity) { /* ... */ }
group.define({
creator: unionOf(member, { schemaAttribute: inferSchema })
});
```
A `unionOf` schema can also be combined with `arrayOf` and `valuesOf` with the same behavior as each supplied with the `schemaAttribute` option.
```javascript
const group = new Schema('groups');
const user = new Schema('users');
const member = unionOf({
users: user,
groups: group
}, { schemaAttribute: 'type' });
group.define({
owner: member,
members: arrayOf(member),
relationships: valuesOf(member)
});
```
### `normalize(obj, schema, [options])`
Normalizes object according to schema.
Passed `schema` should be a nested object reflecting the structure of API response.
You may optionally specify any of the following options:
* `assignEntity` (function): This is useful if your backend emits additional fields, such as separate ID fields, you'd like to delete in the normalized entity. See [the tests](https://github.com/gaearon/normalizr/blob/a0931d7c953b24f8f680b537b5f23a20e8483be1/test/index.js#L89-L200) and the [discussion](https://github.com/gaearon/normalizr/issues/10) for a usage example.
* `mergeIntoEntity` (function): You can use this to resolve conflicts when merging entities with the same key. See [the test](https://github.com/gaearon/normalizr/blob/47ed0ecd973da6fa7c8b2de461e35b293ae52047/test/index.js#L132-L197) and the [discussion](https://github.com/gaearon/normalizr/issues/34) for a usage example.
```javascript
const article = new Schema('articles');
const user = new Schema('users');
article.define({
author: user,
contributors: arrayOf(user),
meta: {
likes: arrayOf({
user: user
})
}
});
// ...
// Normalize one article object
const json = { id: 1, author: ... };
const normalized = normalize(json, article);
// Normalize an array of article objects
const arr = [{ id: 1, author: ... }, ...]
const normalized = normalize(arr, arrayOf(article));
// Normalize an array of article objects, referenced by an object key:
const wrappedArr = { articles: [{ id: 1, author: ... }, ...] }
const normalized = normalize(wrappedArr, {
articles: arrayOf(article)
});
```
## Explanation by Example
Say, you have `/articles` API with the following schema:
```
articles: article*
article: {
author: user,
likers: user*
primary_collection: collection?
collections: collection*
}
collection: {
curator: user
}
```
Without normalizr, your Stores would need to know too much about API response schema.
For example, `UserStore` would include a lot of boilerplate to extract fresh user info when articles are fetched:
```javascript
// Without normalizr, you'd have to do this in every store:
AppDispatcher.register((payload) => {
const { action } = payload;
switch (action.type) {
case ActionTypes.RECEIVE_USERS:
mergeUsers(action.rawUsers);
break;
case ActionTypes.RECEIVE_ARTICLES:
action.rawArticles.forEach(rawArticle => {
mergeUsers([rawArticle.user]);
mergeUsers(rawArticle.likers);
mergeUsers([rawArticle.primaryCollection.curator]);
rawArticle.collections.forEach(rawCollection => {
mergeUsers(rawCollection.curator);
});
});
UserStore.emitChange();
break;
}
});
```
Normalizr solves the problem by converting API responses to a flat form where nested entities are replaced with IDs:
```javascript
```js
{
result: [12, 10, 3, ...],
result: "123",
entities: {
articles: {
12: {
authorId: 3,
likers: [2, 1, 4],
primaryCollection: 12,
collections: [12, 11]
},
...
"articles": {
"123": {
id: "123",
author: "1",
title: "My awesome blog post",
comments: [ "324" ]
}
},
users: {
3: {
name: 'Dan'
},
2: ...,
4: ....
"users": {
"1": { "id": "1", "name": "Paul" }
"2": { "id": "2", "name": "Nicole" }
},
collections: {
12: {
curator: 2,
name: 'Stuff'
},
...
"comments": {
"324": { id: "324", "commenter": "2" }
}

@@ -569,40 +96,8 @@ }

Then `UserStore` code can be rewritten as:
```javascript
// With normalizr, users are always in action.response.entities.users
AppDispatcher.register((payload) => {
const { action } = payload;
if (action.response && action.response.entities && action.response.entities.users) {
mergeUsers(action.response.entities.users);
UserStore.emitChange();
break;
}
});
```
## Dependencies
* Some methods from `lodash`, such as `isObject`, `isEqual` and `mapValues`
None.
## Browser Support
Modern browsers with ES5 environments are supported.
The minimal supported IE version is IE 9.
## Running Tests
```
git clone https://github.com/gaearon/normalizr.git
cd normalizr
yarn install
npm test # run tests once
npm run test:watch # run test watcher
```
## Credits
Normalizr was originally created by [Dan Abramov](http://github.com/gaearon) and inspired by a conversation with [Jing Chen](https://twitter.com/jingc).
It has since received contributions from different [community members](https://github.com/gaearon/normalizr/graphs/contributors).
Normalizr was originally created by [Dan Abramov](http://github.com/gaearon) and inspired by a conversation with [Jing Chen](https://twitter.com/jingc). Since v3, it was completely rewritten and maintained by [Paul Armstrong](https://twitter.com/paularmstrong). It has also received much help, enthusiasm, and contributions from [community members](https://github.com/paularmstrong/normalizr/graphs/contributors).

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc