歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

[譯]JSON數據范式化(normalizr)

摘要 開發復雜的應用時,不可避免會有一些數據相互引用。建議你盡可能地把 state 范式化,不存在嵌套。把所有數據放到一個對象裡,每個數據以 ID 為主鍵,不同數據相互引用時通過 ID 來查找。把 應用的 state 想像成數據庫 。這種方法在 normalizr 文檔裡有詳細闡述。 normalizr...

開發復雜的應用時,不可避免會有一些數據相互引用。建議你盡可能地把 state 范式化,不存在嵌套。把所有數據放到一個對象裡,每個數據以 ID 為主鍵,不同數據相互引用時通過 ID 來查找。把 應用的 state 想像成數據庫 。這種方法在 normalizr 文檔裡有詳細闡述。

normalizr:將嵌套的JSON格式扁平化,方便被Redux利用;

目標

我們的目標是將:

[{
  id: 1,
  title: 'Some Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}, {
  id: 2,
  title: 'Other Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}]
  • 數組的每個對象都糅合了三個維度 文章 、 作者
  • 按照數據范式,應當將這兩個維度拆分出來,兩者的聯系通過id關聯起來即可

我們描述上述的結構: - 返回的是一個數組(array) - 數組的對象中包含另外一個schema —— user

應該比較合理的,應該是轉換成:

{
  result: [1, 2],
  entities: {
    articles: {
      1: {
        id: 1,
        title: 'Some Article',
        author: 1
      },
      2: {
        id: 2,
        title: 'Other Article',
        author: 1
      }
    },
    users: {
      1: {
        id: 1,
        name: 'Dan'
      }
    }
  }
}

如何使用

觀看示例最好的,就是官方的測試文件:https://github.com/gaearon/normalizr/blob/master/test/index.js

先引入 normalizr

import { normalize, Schema, arrayOf } from 'normalizr';  

定義schema

    var article = new Schema('articles'),
        user = new Schema('users'),
        collection = new Schema('collections'),
        feedSchema,
        input;

定義規則:

    article.define({
      author: user,
      collections: arrayOf(collection)
    });

    collection.define({
      curator: user
    });

    feedSchema = {
      feed: arrayOf(article)
    };

測試:

    input = {
      feed: [{
        id: 1,
        title: 'Some Article',
        author: {
          id: 3,
          name: 'Mike Persson'
        },
        collections: [{
          id: 1,
          title: 'Awesome Writing',
          curator: {
            id: 4,
            name: 'Andy Warhol'
          }
        }, {
          id: 7,
          title: 'Even Awesomer',
          curator: {
            id: 100,
            name: 'T.S. Eliot'
          }
        }]
      }, {
        id: 2,
        title: 'Other Article',
        collections: [{
          id: 2,
          title: 'Neverhood',
          curator: {
            id: 120,
            name: 'Ada Lovelace'
          }
        }],
        author: {
          id: 2,
          name: 'Pete Hunt'
        }
      }]
    };

    Object.freeze(input);

    normalize(input, feedSchema).should.eql({
      result: {
        feed: [1, 2]
      },
      entities: {
        articles: {
          1: {
            id: 1,
            title: 'Some Article',
            author: 3,
            collections: [1, 7]
          },
          2: {
            id: 2,
            title: 'Other Article',
            author: 2,
            collections: [2]
          }
        },
        collections: {
          1: {
            id: 1,
            title: 'Awesome Writing',
            curator: 4
          },
          2: {
            id: 2,
            title: 'Neverhood',
            curator: 120
          },
          7: {
            id: 7,
            title: 'Even Awesomer',
            curator: 100
          }
        },
        users: {
          2: {
            id: 2,
            name: 'Pete Hunt'
          },
          3: {
            id: 3,
            name: 'Mike Persson'
          },
          4: {
            id: 4,
            name: 'Andy Warhol'
          },
          100: {
            id: 100,
            name: 'T.S. Eliot'
          },
          120: {
            id: 120,
            name: 'Ada Lovelace'
          }
        }
      }
    });

優勢

假定請求 /articles 返回的數據的schema如下:

articles: article*

article: {  
  author: user,
  likers: user*
  primary_collection: collection?
  collections: collection*
}

collection: {  
  curator: user
}

如果不做范式化,store需要事先知道API的各種結構,比如UserStore會包含很多樣板代碼來獲取新用戶,諸如下面那樣:

  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;
  }

store表示累覺不愛啊!! 每個store都要對返回的 進行各種foreach 才能獲取想要的數據。

來一個范式吧:

const article = new Schema('articles');  
const user = new Schema('users');

article.define({  
  author: user,
  contributors: arrayOf(user),
  meta: {
    likes: arrayOf({
      user: user
    })
  }
});

// ...

const json = getArticleArray();  
const normalized = normalize(json, arrayOf(article));  

經過范式整頓之後,你愛理或者不愛理,users對象總是在 action.entities.users 中:

  const { action } = payload;

  if (action.response && action.response.entities && action.response.entities.users) {
    mergeUsers(action.response.entities.users);
    UserStore.emitChange();
    break;
  }

更多示例(來自測試文件)

規范化單個文件

    var article = new Schema('articles'),
        input;

    input = {
      id: 1,
      title: 'Some Article',
      isFavorite: false
    };

    Object.freeze(input);

    normalize(input, article).should.eql({
      result: 1,
      entities: {
        articles: {
          1: {
            id: 1,
            title: 'Some Article',
            isFavorite: false
          }
        }
      }
    });

規范化內嵌對象,並刪除額外key

有時候後端接口會返回很多額外的字段,甚至會有重復的字段;比如下方示例中 typeId 和 type.id 是重復的;注意方法中 形參key 是經過artcle.define 定義過的。

    var article = new Schema('articles'),
        type = new Schema('types'),
        input;

    // 定義內嵌規則
    article.define({
      type: type
    });

    input = {
      id: 1,
      title: 'Some Article',
      isFavorite: false,
      typeId: 1,
      type: {
        id: 1,
      }
    };

    Object.freeze(input);

    // assignEntity刪除後端返回額外數據的
    var options = {
      assignEntity: function(obj, key, val) {
        obj[key] = val;
        delete obj[key + 'Id'];
      }
    };

    normalize(input, article, options).should.eql({
      result: 1,
      entities: {
        articles: {
          1: {
            id: 1,
            title: 'Some Article',
            isFavorite: false,
            type: 1
          }
        },
        types: {
          1: {
            id: 1
          }
        }
      }
    });

添加額外數據

和上個示例相反的是,mergeIntoEntity 用於將多份同質數據不同信息融合到一起,用於解決沖突。

下方示例中,author 和 reviewer 是同一個人,只是前者留下的聯系方式是手機,後者留下的聯系方式是郵箱,但無論如何都是同一個人;

此時就可以使用 mergeIntoEntity 將兩份數據融合到一起;(注意這裡是用 valueOf規則 )

    var author = new Schema('authors'),
        input;

    input = {
      author: {
        id: 1,
        name: 'Ada Lovelace',
        contact: {
          phone: '555-0100'
        }
      },
      reviewer: {
        id: 1,
        name: 'Ada Lovelace',
        contact: {
          email: '[email protected]'
        }
      }
    }

    Object.freeze(input);

    var options = {
      mergeIntoEntity: function(entityA, entityB, entityKey) {
        var key;

        for (key in entityB) {
          if (!entityB.hasOwnProperty(key)) {
            continue;
          }

          if (!entityA.hasOwnProperty(key) || isEqual(entityA[key], entityB[key])) {
            entityA[key] = entityB[key];
            continue;
          }

          if (isObject(entityA[key]) && isObject(entityB[key])) {
            merge(entityA[key], entityB[key])
            continue;
          }

          console.warn('Unequal data!');
        }
      }
    };

    normalize(input, valuesOf(author), options).should.eql({
      result: {
        author: 1,
        reviewer: 1
      },
      entities: {
        authors: {
          1: {
            id: 1,
            name: 'Ada Lovelace',
            contact: {
              phone: '555-0100',
              email: '[email protected]'
            }
          }
        }
      }
    });

按指定的屬性規范化

有時候對象沒有 id 屬性,或者我們並不想按 id 屬性規范化,可以使用 idAttribute 指定;

下面的例子,就是使用slug作為規范化的key:

  var article = new Schema('articles', { idAttribute: 'slug' }),
        input;

    input = {
      id: 1,
      slug: 'some-article',
      title: 'Some Article',
      isFavorite: false
    };

    Object.freeze(input);

    normalize(input, article).should.eql({
      result: 'some-article',
      entities: {
        articles: {
          'some-article': {
            id: 1,
            slug: 'some-article',
            title: 'Some Article',
            isFavorite: false
          }
        }
      }
    });

創建自定義的屬性

有時候想自己創建一個key,雖然今天和去年創建的文章名稱都是Happy,但明顯是不一樣的,為了按時間區分出來,可以 使用自定義函數生成想要的key 。

    function makeSlug(article) {
      var posted = article.posted,
          title = article.title.toLowerCase().replace(' ', '-');

      return [title, posted.year, posted.month, posted.day].join('-');
    }

    var article = new Schema('articles', { idAttribute: makeSlug }),
        input;

    input = {
      id: 1,
      title: 'Some Article',
      isFavorite: false,
      posted: {
        day: 12,
        month: 3,
        year: 1983
      }
    };

    Object.freeze(input);

    normalize(input, article).should.eql({
      result: 'some-article-1983-3-12',
      entities: {
        articles: {
          'some-article-1983-3-12': {
            id: 1,
            title: 'Some Article',
            isFavorite: false,
            posted: {
              day: 12,
              month: 3,
              year: 1983
            }
          }
        }
      }
    });

規范化數組

後端返回的數據往往是一串數組居多,此時規范化起到很大的作用,規范化的同時將數據壓縮了一遍;

   var article = new Schema('articles'),
        input;

    input = [{
      id: 1,
      title: 'Some Article'
    }, {
      id: 2,
      title: 'Other Article'
    }];

    Object.freeze(input);

    normalize(input, arrayOf(article)).should.eql({
      result: [1, 2],
      entities: {
        articles: {
          1: {
            id: 1,
            title: 'Some Article'
          },
          2: {
            id: 2,
            title: 'Other Article'
          }
        }
      }
    });

抽取多個schema

上面講的情形比較簡單,只涉及抽出結果是單個schema的情形;現實中,你往往想抽象出多個schema,比如下方,我想抽離出 tutorials(教程) 和articles(文章)兩個 schema,此時需要 通過 schemaAttribute 選項指定區分這兩個 schema 的字段 :

    var article = new Schema('articles'),
        tutorial = new Schema('tutorials'),
        articleOrTutorial = { articles: article, tutorials: tutorial },
        input;

    input = [{
      id: 1,
      type: 'articles',
      title: 'Some Article'
    }, {
      id: 1,
      type: 'tutorials',
      title: 'Some Tutorial'
    }];

    Object.freeze(input);

    normalize(input, arrayOf(articleOrTutorial, { schemaAttribute: 'type' })).should.eql({
      result: [
        {id: 1, schema: 'articles'},
        {id: 1, schema: 'tutorials'}
      ],
      entities: {
        articles: {
          1: {
            id: 1,
            type: 'articles',
            title: 'Some Article'
          }
        },
        tutorials: {
          1: {
            id: 1,
            type: 'tutorials',
            title: 'Some Tutorial'
          }
        }
      }
    });

這個示例中,雖然文章的id都是1,但很明顯它們是不同的文章,因為一篇是普通文章,一篇是教程文章;因此要按schema維度抽離數據;

這裡的 arrayOf(articleOrTutorial) 中的 articleOrTutorial 是包含多個屬性的對象,這表示 input 應該是 articleOrTutorial 中的一種情況;

有時候原始數據屬性 和 我們定義的有些差別,此時可以將 schemaAttribute 的值設成函數,將原始屬性經過適當加工;比如原始屬性是tutorial , 而抽離出的 schema 名字為 tutorials ,相差一個s

    function guessSchema(item) {
      return item.type + 's';
    }

    var article = new Schema('articles'),
        tutorial = new Schema('tutorials'),
        articleOrTutorial = { articles: article, tutorials: tutorial },
        input;

    input = [{
      id: 1,
      type: 'article',
      title: 'Some Article'
    }, {
      id: 1,
      type: 'tutorial',
      title: 'Some Tutorial'
    }];

    Object.freeze(input);

    normalize(input, arrayOf(articleOrTutorial, { schemaAttribute: guessSchema })).should.eql({
      result: [
        { id: 1, schema: 'articles' },
        { id: 1, schema: 'tutorials' }
      ],
      entities: {
        articles: {
          1: {
            id: 1,
            type: 'article',
            title: 'Some Article'
          }
        },
        tutorials: {
          1: {
            id: 1,
            type: 'tutorial',
            title: 'Some Tutorial'
          }
        }
      }
    });

上述是數組情況,針對普通的對象也是可以的,將規則 改成 valueOf 即可:

   var article = new Schema('articles'),
        tutorial = new Schema('tutorials'),
        articleOrTutorial = { articles: article, tutorials: tutorial },
        input;

    input = {
      one: {
        id: 1,
        type: 'articles',
        title: 'Some Article'
      },
      two: {
        id: 2,
        type: 'articles',
        title: 'Another Article'
      },
      three: {
        id: 1,
        type: 'tutorials',
        title: 'Some Tutorial'
      }
    };

    Object.freeze(input);

    normalize(input, valuesOf(articleOrTutorial, { schemaAttribute: 'type' })).should.eql({
      result: {
        one: {id: 1, schema: 'articles'},
        two: {id: 2, schema: 'articles'},
        three: {id: 1, schema: 'tutorials'}
      },
      entities: {
        articles: {
          1: {
            id: 1,
            type: 'articles',
            title: 'Some Article'
          },
          2: {
            id: 2,
            type: 'articles',
            title: 'Another Article'
          }
        },
        tutorials: {
          1: {
            id: 1,
            type: 'tutorials',
            title: 'Some Tutorial'
          }
        }
      }
    });

schemaAttribute 是函數的情況就不列舉了,和上述一致;

規范化內嵌情形

上面的對象比較簡單,原本就是扁平化的;如果對象格式稍微復雜一些,比如每篇文章有多個作者的情形。此時需要使用 define 事先聲明 schema 之間的層級關系:

    var article = new Schema('articles'),
        user = new Schema('users'),
        input;

    article.define({
      author: user
    });

    input = {
      id: 1,
      title: 'Some Article',
      author: {
        id: 3,
        name: 'Mike Persson'
      }
    };

    Object.freeze(input);

    normalize(input, article).should.eql({
      result: 1,
      entities: {
        articles: {
          1: {
            id: 1,
            title: 'Some Article',
            author: 3
          }
        },
        users: {
          3: {
            id: 3,
            name: 'Mike Persson'
          }
        }
      }
    });

上面是不是覺得簡單了?那麼給你一個比較復雜的情形,萬變不離其宗。我們最終想抽離出 articles 、users 以及 collections 這三個 schema,所以只要定義這三個schema就行了,

然後使用 define 方法聲明這三個schema之間千絲萬縷的關系;

最外層的feed只是屬性,並不需要定義;

    var article = new Schema('articles'),
        user = new Schema('users'),
        collection = new Schema('collections'),
        feedSchema,
        input;

    article.define({
      author: user,
      collections: arrayOf(collection)
    });

    collection.define({
      curator: user
    });

    feedSchema = {
      feed: arrayOf(article)
    };

    input = {
      feed: [{
        id: 1,
        title: 'Some Article',
        author: {
          id: 3,
          name: 'Mike Persson'
        },
        collections: [{
          id: 1,
          title: 'Awesome Writing',
          curator: {
            id: 4,
            name: 'Andy Warhol'
          }
        }, {
          id: 7,
          title: 'Even Awesomer',
          curator: {
            id: 100,
            name: 'T.S. Eliot'
          }
        }]
      }, {
        id: 2,
        title: 'Other Article',
        collections: [{
          id: 2,
          title: 'Neverhood',
          curator: {
            id: 120,
            name: 'Ada Lovelace'
          }
        }],
        author: {
          id: 2,
          name: 'Pete Hunt'
        }
      }]
    };

    Object.freeze(input);

    normalize(input, feedSchema).should.eql({
      result: {
        feed: [1, 2]
      },
      entities: {
        articles: {
          1: {
            id: 1,
            title: 'Some Article',
            author: 3,
            collections: [1, 7]
          },
          2: {
            id: 2,
            title: 'Other Article',
            author: 2,
            collections: [2]
          }
        },
        collections: {
          1: {
            id: 1,
            title: 'Awesome Writing',
            curator: 4
          },
          2: {
            id: 2,
            title: 'Neverhood',
            curator: 120
          },
          7: {
            id: 7,
            title: 'Even Awesomer',
            curator: 100
          }
        },
        users: {
          2: {
            id: 2,
            name: 'Pete Hunt'
          },
          3: {
            id: 3,
            name: 'Mike Persson'
          },
          4: {
            id: 4,
            name: 'Andy Warhol'
          },
          100: {
            id: 100,
            name: 'T.S. Eliot'
          },
          120: {
            id: 120,
            name: 'Ada Lovelace'
          }
        }
      }
    });

內嵌+數組傾斜

   var article = new Schema('articles'),
        tutorial = new Schema('tutorials'),
        articleOrTutorial = { articles: article, tutorials: tutorial },
        user = new Schema('users'),
        collection = new Schema('collections'),
        feedSchema,
        input;

    article.define({
      author: user,
      collections: arrayOf(collection)
    });

    tutorial.define({
      author: user,
      collections: arrayOf(collection)
    });

    collection.define({
      curator: user
    });

    feedSchema = {
      feed: arrayOf(articleOrTutorial, { schemaAttribute: 'type' })
    };

    input = {
      feed: [{
        id: 1,
        type: 'articles',
        title: 'Some Article',
        author: {
          id: 3,
          name: 'Mike Persson'
        },
        collections: [{
          id: 1,
          title: 'Awesome Writing',
          curator: {
            id: 4,
            name: 'Andy Warhol'
          }
        }, {
          id: 7,
          title: 'Even Awesomer',
          curator: {
            id: 100,
            name: 'T.S. Eliot'
          }
        }]
      }, {
        id: 1,
        type: 'tutorials',
        title: 'Some Tutorial',
        collections: [{
          id: 2,
          title: 'Neverhood',
          curator: {
            id: 120,
            name: 'Ada Lovelace'
          }
        }],
        author: {
          id: 2,
          name: 'Pete Hunt'
        }
      }]
    };

    Object.freeze(input);

    normalize(input, feedSchema).should.eql({
      result: {
        feed: [
          { id: 1, schema: 'articles' },
          { id: 1, schema: 'tutorials' }
        ]
      },
      entities: {
        articles: {
          1: {
            id: 1,
            type: 'articles',
            title: 'Some Article',
            author: 3,
            collections: [1, 7]
          }
        },
        tutorials: {
          1: {
            id: 1,
            type: 'tutorials',
            title: 'Some Tutorial',
            author: 2,
            collections: [2]
          }
        },
        collections: {
          1: {
            id: 1,
            title: 'Awesome Writing',
            curator: 4
          },
          2: {
            id: 2,
            title: 'Neverhood',
            curator: 120
          },
          7: {
            id: 7,
            title: 'Even Awesomer',
            curator: 100
          }
        },
        users: {
          2: {
            id: 2,
            name: 'Pete Hunt'
          },
          3: {
            id: 3,
            name: 'Mike Persson'
          },
          4: {
            id: 4,
            name: 'Andy Warhol'
          },
          100: {
            id: 100,
            name: 'T.S. Eliot'
          },
          120: {
            id: 120,
            name: 'Ada Lovelace'
          }
        }
      }
    });

內嵌 + 對象(再內嵌)

看到下面的 valuesOf(arrayOf(user)) 了沒有,它表示該屬性是一個對象,對象裡面各個數組值是 User對象數組;

    var article = new Schema('articles'),
        user = new Schema('users'),
        feedSchema,
        input;

    article.define({
      collaborators: valuesOf(arrayOf(user))
    });

    feedSchema = {
      feed: arrayOf(article),
      suggestions: valuesOf(arrayOf(article))
    };

    input = {
      feed: [{
        id: 1,
        title: 'Some Article',
        collaborators: {
          authors: [{
            id: 3,
            name: 'Mike Persson'
          }],
          reviewers: [{
            id: 2,
            name: 'Pete Hunt'
          }]
        }
      }, {
        id: 2,
        title: 'Other Article',
        collaborators: {
          authors: [{
            id: 2,
            name: 'Pete Hunt'
          }]
        }
      }, {
        id: 3,
        title: 'Last Article'
      }],
      suggestions: {
        1: [{
          id: 2,
          title: 'Other Article',
          collaborators: {
            authors: [{
              id: 2,
              name: 'Pete Hunt'
            }]
          }
        }, {
          id: 3,
          title: 'Last Article'
        }]
      }
    };

    Object.freeze(input);

    normalize(input, feedSchema).should.eql({
      result: {
        feed: [1, 2, 3],
        suggestions: {
          1: [2, 3]
        }
      },
      entities: {
        articles: {
          1: {
            id: 1,
            title: 'Some Article',
            collaborators: {
              authors: [3],
              reviewers: [2]
            }
          },
          2: {
            id: 2,
            title: 'Other Article',
            collaborators: {
              authors: [2]
            }
          },
          3: {
            id: 3,
            title: 'Last Article'
          }
        },
        users: {
          2: {
            id: 2,
            name: 'Pete Hunt'
          },
          3: {
            id: 3,
            name: 'Mike Persson'
          }
        }
      }
    });

還有更加復雜的,這次用上 valuesOf(userOrGroup, { schemaAttribute: 'type' }) 了:

    var article = new Schema('articles'),
        user = new Schema('users'),
        group = new Schema('groups'),
        userOrGroup = { users: user, groups: group },
        feedSchema,
        input;

    article.define({
      collaborators: valuesOf(userOrGroup, { schemaAttribute: 'type' })
    });

    feedSchema = {
      feed: arrayOf(article),
      suggestions: valuesOf(arrayOf(article))
    };

    input = {
      feed: [{
        id: 1,
        title: 'Some Article',
        collaborators: {
          author: {
            id: 3,
            type: 'users',
            name: 'Mike Persson'
          },
          reviewer: {
            id: 2,
            type: 'groups',
            name: 'Reviewer Group'
          }
        }
      }, {
        id: 2,
        title: 'Other Article',
        collaborators: {
          author: {
            id: 2,
            type: 'users',
            name: 'Pete Hunt'
          }
        }
      }, {
        id: 3,
        title: 'Last Article'
      }],
      suggestions: {
        1: [{
          id: 2,
          title: 'Other Article'
        }, {
          id: 3,
          title: 'Last Article'
        }]
      }
    };

    Object.freeze(input);

    normalize(input, feedSchema).should.eql({
      result: {
        feed: [1, 2, 3],
        suggestions: {
          1: [2, 3]
        }
      },
      entities: {
        articles: {
          1: {
            id: 1,
            title: 'Some Article',
            collaborators: {
              author: { id: 3, schema: 'users' },
              reviewer: { id: 2, schema: 'groups' }
            }
          },
          2: {
            id: 2,
            title: 'Other Article',
            collaborators: {
              author: { id: 2, schema: 'users' }
            }
          },
          3: {
            id: 3,
            title: 'Last Article'
          }
        },
        users: {
          2: {
            id: 2,
            type: 'users',
            name: 'Pete Hunt'
          },
          3: {
            id: 3,
            type: 'users',
            name: 'Mike Persson'
          }
        },
        groups: {
          2: {
            id: 2,
            type: 'groups',
            name: 'Reviewer Group'
          }
        }
      }
    });

遞歸調用

比如某某人關注了另外的人,用戶 寫了一系列文章,該文章 被其他用戶 訂閱就是這種情況:

    var article = new Schema('articles'),
        user = new Schema('users'),
        collection = new Schema('collections'),
        feedSchema,
        input;

    user.define({
      articles: arrayOf(article)
    });

    article.define({
      collections: arrayOf(collection)
    });

    collection.define({
      subscribers: arrayOf(user)
    });

    feedSchema = {
      feed: arrayOf(article)
    };

    input = {
      feed: [{
        id: 1,
        title: 'Some Article',
        collections: [{
          id: 1,
          title: 'Awesome Writing',
          subscribers: [{
            id: 4,
            name: 'Andy Warhol',
            articles: [{
              id: 1,
              title: 'Some Article'
            }]
          }, {
            id: 100,
            name: 'T.S. Eliot',
            articles: [{
              id: 1,
              title: 'Some Article'
            }]
          }]
        }, {
          id: 7,
          title: 'Even Awesomer',
          subscribers: [{
            id: 100,
            name: 'T.S. Eliot',
            articles: [{
              id: 1,
              title: 'Some Article'
            }]
          }]
        }]
      }]
    };

    Object.freeze(input);

    normalize(input, feedSchema).should.eql({
      result: {
        feed: [1]
      },
      entities: {
        articles: {
          1: {
            id: 1,
            title: 'Some Article',
            collections: [1, 7]
          }
        },
        collections: {
          1: {
            id: 1,
            title: 'Awesome Writing',
            subscribers: [4, 100]
          },
          7: {
            id: 7,
            title: 'Even Awesomer',
            subscribers: [100]
          }
        },
        users: {
          4: {
            id: 4,
            name: 'Andy Warhol',
            articles: [1]
          },
          100: {
            id: 100,
            name: 'T.S. Eliot',
            articles: [1]
          }
        }
      }
    });

上面還算好的,有些schema直接就遞歸聲明了,比如 兒女和父母 的關系:

    var user = new Schema('users'),
        input;

    user.define({
      parent: user
    });

    input = {
      id: 1,
      name: 'Andy Warhol',
      parent: {
        id: 7,
        name: 'Tom Dale',
        parent: {
          id: 4,
          name: 'Pete Hunt'
        }
      }
    };

    Object.freeze(input);

    normalize(input, user).should.eql({
      result: 1,
      entities: {
        users: {
          1: {
            id: 1,
            name: 'Andy Warhol',
            parent: 7
          },
          7: {
            id: 7,
            name: 'Tom Dale',
            parent: 4
          },
          4: {
            id: 4,
            name: 'Pete Hunt'
          }
        }
      }
    });

自動merge屬性

在一個數組裡面,如果id屬性一致,會自動抽取並合屬性成一個:

    var writer = new Schema('writers'),
        book = new Schema('books'),
        schema = arrayOf(writer),
        input;

    writer.define({
      books: arrayOf(book)
    });

    input = [{
      id: 3,
      name: 'Jo Rowling',
      isBritish: true,
      location: {
        x: 100,
        y: 200,
        nested: ['hello', {
          world: true
        }]
      },
      books: [{
        id: 1,
        soldWell: true,
        name: 'Harry Potter'
      }]
    }, {
      id: 3,
      name: 'Jo Rowling',
      bio: 'writer',
      location: {
        x: 100,
        y: 200,
        nested: ['hello', {
          world: true
        }]
      },
      books: [{
        id: 1,
        isAwesome: true,
        name: 'Harry Potter'
      }]
    }];

    normalize(input, schema).should.eql({
      result: [3, 3],
      entities: {
        writers: {
          3: {
            id: 3,
            isBritish: true,
            name: 'Jo Rowling',
            bio: 'writer',
            books: [1],
            location: {
              x: 100,
              y: 200,
              nested: ['hello', {
                world: true
              }]
            }
          }
        },
        books: {
          1: {
            id: 1,
            isAwesome: true,
            soldWell: true,
            name: 'Harry Potter'
          }
        }
      }
    });

如果合並過程中有沖突會有提示,並自動剔除沖突的屬性;比如下方同一個作者寫的書,一個對象裡描述“賣得好”,而在另外一個對象裡卻描述“賣得差”,明顯是有問題的:

    var writer = new Schema('writers'),
        book = new Schema('books'),
        schema = arrayOf(writer),
        input;

    writer.define({
      books: arrayOf(book)
    });

    input = [{
      id: 3,
      name: 'Jo Rowling',
      books: [{
        id: 1,
        soldWell: true,
        name: 'Harry Potter'
      }]
    }, {
      id: 3,
      name: 'Jo Rowling',
      books: [{
        id: 1,
        soldWell: false,
        name: 'Harry Potter'
      }]
    }];

    var warnCalled = false,
        realConsoleWarn;

    function mockWarn() {
      warnCalled = true;
    }

    realConsoleWarn = console.warn;
    console.warn = mockWarn;

    normalize(input, schema).should.eql({
      result: [3, 3],
      entities: {
        writers: {
          3: {
            id: 3,
            name: 'Jo Rowling',
            books: [1]
          }
        },
        books: {
          1: {
            id: 1,
            soldWell: true,
            name: 'Harry Potter'
          }
        }
      }
    });

    warnCalled.should.eq(true);
    console.warn = realConsoleWarn;

傳入不存在的schema規范

如果應用的schma規范不存在,你還傳入,就會創建一個新的父屬性:

    var writer = new Schema('writers'),
        schema = writer,
        input;
    input = {
      id: 'constructor',
      name: 'Constructor',
      isAwesome: true
    };

    normalize(input, schema).should.eql({
      result: 'constructor',
      entities: {
        writers: {
          constructor: {
            id: 'constructor',
            name: 'Constructor',
            isAwesome: true
          }
        }
      }

Copyright © Linux教程網 All Rights Reserved