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

jopen 8年前發布 | 11K 次閱讀 JSON Ada

開發復雜的應用時,不可避免會有一些數據相互引用。建議你盡可能地把 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: 'ada@lovelace.com'
        }
      }
    }

    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: 'ada@lovelace.com'
            }
          }
        }
      }
    });

按指定的屬性規范化

有時候對象沒有 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'
      }
    }
  }
});</code></pre> 

上面是不是覺得簡單了?那么給你一個比較復雜的情形,萬變不離其宗。我們最終想抽離出 articlesusers 以及  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'
      }
    }
  }
});</code></pre> 

內嵌+數組傾斜

   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'
      }
    }
  }
});</code></pre> 

內嵌 + 對象(再內嵌)

看到下面的 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'
      }
    }
  }
});</code></pre> 

還有更加復雜的,這次用上 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'
      }
    }
  }
});</code></pre> 

遞歸調用

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

    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]
      }
    }
  }
});</code></pre> 

上面還算好的,有些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'
      }
    }
  }
});</code></pre> 

自動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'
      }
    }
  }
});</code></pre> 

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

    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;</code></pre> 

傳入不存在的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
      }
    }
  }</code></pre> </div>

來自: https://yq.aliyun.com/articles/3168

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!