Sense categoria

normalize example

Normalizr

narmalizr 사용법을 익혀 본다.

목차

Definition

normalize: 표준화/일반화 하다.

어디선가 본 가장 와닿는 정의는 다음과 같다.
: a way of cleaning up JSON data consisting of many deeply nested objects

깊게 중첩된 오브젝트로 구성된 JSON 데이터를 정리하는 방법

normalizrMotivationSolution을 살펴보면 알 수 있듯이, 복잡한 JSON Object를 정규화 하기 위한 Library 정도 되겠다.

Motivation

Many APIs, public or not, return JSON data that has deeply nested objects. Using data in this kind of structure is often very difficult for JavaScript applications, especially those using Flux or Redux.

많은 API들은 중첩된 객체 형태의 JSON을 return 한다. 이런 구조의 데이터를 사용하는 것은 javascript applications, 특히 Flux 또는 Redux를 사용 하기 어렵다.

Solution

Normalizr is a small, but powerful utility for taking JSON with a schema definition and returning nested entities with their IDs, gathered in dictionaries.

Normalizr은 JSON을 스키마 정의로 사용하고, Dictionary에 저장된 ID로 중첩 된 항목을 반환하는 작지만 강력한 유틸리티.

Execution of normalizr

이론을 글로 읽는 것 보다는 실행 전/후를 살펴보는게 훨씬 빠르게 이해 할 수 있을 것이다. ㄱㄱㅆ

normalizr (github, paularmstrong) 예제를 테스트하기 편하게 재작성 해 보았다.

Environment

ECMAScript Module(ESM)을 사용하고 있어, node.js 는 기본적으로 CommonJS Module loading을 사용하기 때문에 babel을 사용해서 실행한다.
node.js v8.9.1의 –experimental-modules flag 사용을 해 보려고 했으나, 결국 실패해서 포스팅 재작성

Project init

1
2
3
4
> mkdir normalizr_khackskjs
> cd normalizr_khackskjs
> npm init -y
> npm i --save normalizr

babel & normalizr setting

@babel/node를 통해서 ESM을 tranpile과 동시에 실행하도록 한다.

  1. babel-cli를 설치
  2. @babel/core, @babel/node 설치
  3. transpile presets 설치
  4. normalizr module 설치
1
2
3
4
> npm i -g babel-cli
> npm i --save-dev @babel/core @babel/node
> npm i --save-dev babel-preset-es2015 babel-preset-stage-1
> npm i --save normalizr

.babelrc file을 생성 한 후 tranpile 시 preset options을 지정한다.

./.babelrc
1
2
3
{
"presets": ["es2015", "stage-1"]
}

Coding

input.json 파일을 읽어서, normalize 한 후 output.json file에 쓰는 예제이다.

아래 input.json, schema.js, input.js source를 그대로 붙여넣어 파일을 생성한다.

./input.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[{
"id": "1",
"title": "My first post!",
"author": {
"id": "123",
"name": "Paul"
},
"comments": [{
"id": "249",
"content": "Nice post!",
"commenter": {
"id": "245",
"name": "Jane"
}
},
{
"id": "250",
"content": "Thanks!",
"commenter": {
"id": "123",
"name": "Paul"
}
}
]
},
{
"id": "2",
"title": "This other post",
"author": {
"id": "123",
"name": "Paul"
},
"comments": [{
"id": "251",
"content": "Your other post was nicer",
"commenter": {
"id": "245",
"name": "Jane"
}
},
{
"id": "252",
"content": "I am a spammer!",
"commenter": {
"id": "246",
"name": "Spambot5000"
}
}
]
}
]
./schema.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { schema } from 'normalizr';
const userProcessStrategy = (value, parent, key) => {
switch (key) {
case 'author':
return { ...value, posts: [ parent.id ] };
case 'commenter':
return { ...value, comments: [ parent.id ] };
default:
return { ...value };
}
};
const userMergeStrategy = (entityA, entityB) => {
return {
...entityA,
...entityB,
posts: [ ...(entityA.posts || []), ...(entityB.posts || []) ],
comments: [ ...(entityA.comments || []), ...(entityB.comments || []) ]
};
};
const user = new schema.Entity('users', {}, {
mergeStrategy: userMergeStrategy,
processStrategy: userProcessStrategy
});
const comment = new schema.Entity('comments', {
commenter: user
}, {
processStrategy: (value, parent, key) => {
return { ...value, post: parent.id };
}
});
const post = new schema.Entity('posts', {
author: user,
comments: [ comment ]
});
export default [ post ];
./index.js
1
2
3
4
5
6
7
8
9
10
11
import input from './input.json';
import fs from 'fs';
import { normalize } from 'normalizr';
import path from 'path';
import postsSchema from './schema';
const normalizedData = normalize(input, postsSchema);
const output = JSON.stringify(normalizedData, null, 2);
fs.writeFileSync(path.resolve(__dirname, './output.json'), output);
console.log('./output.json has been created');

Run

1
> babel-node index.js

Result

output.json 파일이 생성되며, 결과는 다음과 같다.

./output.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
{
"entities": {
"users": {
"123": {
"id": "123",
"name": "Paul",
"posts": [
"1",
"2"
],
"comments": [
"250"
]
},
"245": {
"id": "245",
"name": "Jane",
"comments": [
"249",
"251"
],
"posts": []
},
"246": {
"id": "246",
"name": "Spambot5000",
"comments": [
"252"
]
}
},
"comments": {
"249": {
"id": "249",
"content": "Nice post!",
"commenter": "245",
"post": "1"
},
"250": {
"id": "250",
"content": "Thanks!",
"commenter": "123",
"post": "1"
},
"251": {
"id": "251",
"content": "Your other post was nicer",
"commenter": "245",
"post": "2"
},
"252": {
"id": "252",
"content": "I am a spammer!",
"commenter": "246",
"post": "2"
}
},
"posts": {
"1": {
"id": "1",
"title": "My first post!",
"author": "123",
"comments": [
"249",
"250"
]
},
"2": {
"id": "2",
"title": "This other post",
"author": "123",
"comments": [
"251",
"252"
]
}
}
},
"result": [
"1",
"2"
]
}

Into the Normalizr

normalizr interface 부분을 간략히 살펴본 후 예제를 통해서 사용법을 익혀본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
declare namespace schema {
// 중략
}
export type Schema = schema.Array | schema.Entity |
schema.Object | schema.Union | schema.Values |
schema.Array[] | schema.Entity[] | schema.Object[] |
schema.Union[] | schema.Values[] | {[key: string]: Schema};
export function normalize(data: any, schema: Schema)
: { entities: any, result: any };
export function denormalize(input: any, schema: Schema, entities: any)
: any;

normalize function

normalize(data: any, schema: Schema): { entities: any, result: any }

Normalizes input data per the schema definition provided.

  • data: required Input JSON (or plain JS object) data that needs normalization.
  • schema: required A schema definition

GitHub에서 normalize function을 위와같이 설명하고 있다. 그렇다고 한다-ㅅ-

normalize function을 사용하기 위해서는 normalize 할 원본 데이터(JSON or plain JS object)와, 해당 원본 데이터의 schema를 정의한 Scmhema를 parameter로 전달해야 한다.

export type Schema =를 이루는 항목들을 보면 알 수 있듯 Schema는 모두 namespace schema를 이용해서 만들어야 한다.
그리고 보통 schema.Entity, schema.Array 그리고 {[key: string]: Schema}를 사용 할 것으로 보인다.

무슨 말인고 하니, input data가 어떻게 이루어져 있는지를 정의하고, 필요에 따라서는 몇가지 옵션을 통해서 Entity를 만져주면 된다.

example of normalize function

input data의 구조를 분해해서 Entity, Schema 를 작성하면 된다.

demo/01.js
1
2
3
4
5
6
7
import { normalize, schema } from 'normalizr';
const input = { books: [{ id: 1, title: 'rework' }, { id: 2, title: '대한민국이 묻는다'}] },
book = new schema.Entity('book'),
bookSchema = { books: [book] },
normalized = normalize(input, bookSchema);
console.log(require('util').inspect(normalized, { depth: null }));

우리의 관심사는 book 이라는 Entity에 들어있는 값 들이다.
하지만 input data에 book은 books 라는 property에 Array 형태로 존재한다.
따라서 이를 정리하면 다음과 같다.

  1. input data 는 object 형태로 books라는 propertyArray를 가지고 있다.

    input = { books: [] }

  2. Arrayitemobject이며, 이를 book 으로 사용 하도록 한다.

    book = { id: 1, title: 'rework' }

위의 순서에 따라서 정의(line 3, 4) 한 후, normalize function에 전달하면 된다.

결과는 아래와 같다.

1
2
3
4
5
{ entities:
{ book:
{ '1': { id: 1, title: 'rework' },
'2': { id: 2, title: '대한민국이 묻는다' } } },
result: { books: [ 1, 2 ] } }

아주 심플한 예제는 이정도에서 마치자.

Dive into the normalizr

앞서 최초 실행 해 봤던 예제에 대한 분석.

To be continue..

processStrategy(value, parent, key): Strategy to use when pre-processing the entity.
Use this method to add extra data, defaults, and/or completely change the entity before normalization is complete.
Defaults to returning a shallow copy of the input entity.
Note: It is recommended to always return a copy of your input and not modify the original.
The function accepts the following arguments, in order:
value: The input value of the entity.
parent: The parent object of the input array.
key: The key at which the input array appears on the parent object.

references

paularmstrong/normalizr (github)

Compartir