【问题标题】:Load Google product taxonomy into Firestore using node使用节点将 Google 产品分类加载到 Firestore
【发布时间】:2020-04-02 03:09:41
【问题描述】:

我正在尝试将Google product taxonomy 加载到 Firestore 文档中,我认为这主要意味着将其转换为 JSON。这是分类的示例:

1 - Animals & Pet Supplies
3237 - Animals & Pet Supplies > Live Animals
2 - Animals & Pet Supplies > Pet Supplies
3 - Animals & Pet Supplies > Pet Supplies > Bird Supplies
7385 - Animals & Pet Supplies > Pet Supplies > Bird Supplies > Bird Cage Accessories

我可以弄清楚如何处理第一级类别(见下面的代码),但我不知道如何向下递归其他类别。

const taxonomy = { version: '2019-07-10', categories: []}

for (const line of file) {
    // parse the line using -, > as dividers to create array lineItems
    const categoryLevel1 = {}
    categoryLevel1.id = lineItems[0]
    categoryLevel1.name = lineItems[1]
    categoryLevel1.categories = []
    if (!taxonomy.categories.find(category => category.name === categoryLevel1.name)) {
        taxonomy.categories.push(categoryLevel1)             
    }
}

【问题讨论】:

    标签: javascript node.js json recursion


    【解决方案1】:

    我正在尝试将Google product taxonomy 加载到 Firestore 文档中,我认为这主要意味着将其转换为 JSON。

    我建议第一次创建嵌套对象。
    (您始终可以重新处理它们以获得最终所需的结构。)

    {
      "Animals & Pet Supplies": {
        "id": "1",
        "Live Animals": {
          "id": "3237"
        },
        "Pet Supplies": {
          "id": "2",
          "Bird Supplies": {
            "id": "3",
            "Bird Cage Accessories": {
              "id": "7385"
            }
          }
        }
      }
    }
    

    为什么?在我看来,这些行可以很容易地转换为嵌套对象,然后您可以合并在一起。

    以下几行:

    [ "1 - Animals & Pet Supplies",
      "3237 - Animals & Pet Supplies > Live Animals" ]
    

    可以转化为:

    [ {"Animals & Pet Supplies": {"id": 1}}
      {"Animals & Pet Supplies": {"Live Animals": {"id": 3237}}}]
    

    然后合并到:

    {
      "Animals & Pet Supplies": {
        "id": 1,
        "Live Animals": {
          "id": 3237
        }
      }
    }
    

    如何?

    首先让我们创建两个函数来获取 id 和每个类别

    • 要获取 id,我们必须拆分“-”字符。
    • 要获得必须在“>”字符上拆分的类别。
    • 在这两种情况下,我们都希望修剪结果。

    让我们首先创建一个通用的curried函数:

    const splitBy = sep => str =>
      str.split(sep).map(x => x.trim());
    

    因为它是柯里化的,我们可以在它之上构建两个专门的函数:

    const splitLine = splitBy('-');
    const splitCategories = splitBy('>');
    
    splitLine('1 - Animals & Pet Supplies');
    //=> [ '1', 'Animals & Pet Supplies' ]
    
    splitCategories('Animals & Pet Supplies > Live Animals');
    //=> [ 'Animals & Pet Supplies', 'Live Animals' ]
    

    然后让我们将每一行转换成一个数据结构,让我们可以创建嵌套对象:

    以下几行:

    [ "1 - Animals & Pet Supplies",
      "3237 - Animals & Pet Supplies > Live Animals" ]
    

    可以转换成对,其中每对代表一个对象,一对可以包含在另一个对象中:

    [ ["Animals & Pet Supplies", 1]
      ["Animals & Pet Supplies", ["Live Animals", 3237]]]
    

    此函数将在将平面数组转换为对象之前将其转换为嵌套对:

    const nest = xs =>
      xs.length === 2
        ? typeof xs[1] === 'string'
          ? {[xs[0]]: {id: xs[1]}}
          : {[xs[0]]: nest(xs[1])}
        : nest([xs[0], xs.slice(1)]);
    
    nest(["Animals & Pet Supplies", "Live Animals", 3237]);
    // (internally) => ["Animals & Pet Supplies", ["Live Animals", 3237]]
    // (final output) => {"Animals & Pet Supplies": {"Live Animals": {"id": 3237}}}
    

    要合并这个对象数组,我将使用deepmerge。 (但您可以使用其他任何东西,只要它允许 deep 合并而不是 shallow 合并,就像您使用扩展 ... 运算符或 Object.assign 获得的那样)

    deepmerge.all(
      [ {"Animals & Pet Supplies": {"id": 1}}
        {"Animals & Pet Supplies": {"Live Animals": {"id": 3237}}}]);
    
    //=>    {
    //=>      "Animals & Pet Supplies": {
    //=>        "id": "1",
    //=>        "Live Animals": {
    //=>          "id": "3237"
    //=>        }
    //=>      }
    //=>    }
    

    这是一个函数,它将你的行作为一个数组并返回一个嵌套类别的对象:

    const load = lines =>
      // put all lines into a "container"
      // we want to process all lines all the time as opposed to each line individually
      [lines]
        // separate id and categories
        // e.g ['3237', 'Animals & Pet Supplies > Live Animals']
        .map(lines => lines.map(splitLine))
        // split categories and put id last
        // e.g. ['Animals & Pet Supplies', 'Live Animals', 3237]
        .map(lines => lines.map(([id, cats]) => splitCategories(cats).concat(id)))
        // created nested objects
        // e.g. {"Animals & Pet Supplies": {"Live Animals": {"id": 3237}}}
        .map(lines => lines.map(nest))
        // merge all objects into one
        .map(lines => deepmerge.all(lines))
        // pop the result out of the container
        .pop();
    
    load(
      [ "1 - Animals & Pet Supplies",
        "3237 - Animals & Pet Supplies > Live Animals" ]);
    
    //=>    {
    //=>      "Animals & Pet Supplies": {
    //=>        "id": "1",
    //=>        "Live Animals": {
    //=>          "id": "3237"
    //=>        }
    //=>      }
    //=>    }
    

    总而言之:

    const splitBy = sep => str =>
      str.split(sep).map(x => x.trim());
    
    const splitLine = splitBy('-');
    const splitCategories = splitBy('>');
    
    const nest = xs =>
      xs.length === 2
        ? typeof xs[1] === 'string'
          ? {[xs[0]]: {id: xs[1]}}
          : {[xs[0]]: nest(xs[1])}
        : nest([xs[0], xs.slice(1)]);
    
    const load = lines =>
      // put all lines into a "container"
      // we want to process all lines all the time as opposed to each line individually
      [lines]
        // separate id and categories
        // e.g ['3237', 'Animals & Pet Supplies > Live Animals']
        .map(lines => lines.map(splitLine))
        // split categories and put id last
        // e.g. ['Animals & Pet Supplies', 'Live Animals', 3237]
        .map(lines => lines.map(([id, cats]) => splitCategories(cats).concat(id)))
        // created nested objects
        // e.g. {"Animals & Pet Supplies": {"Live Animals": {"id": 3237}}}
        .map(lines => lines.map(nest))
        // merge all objects into one
        .map(lines => deepmerge.all(lines))
        // pop the result out of the container
        .pop();
    
        
    console.log(
      JSON.stringify(
        load(file_content),
        null,
        2
      )
    )
    <script src="https://unpkg.com/deepmerge@3.0.0/dist/umd.js"></script>
    <script>
    const file_content = [
      '1 - Animals & Pet Supplies',
      '3237 - Animals & Pet Supplies > Live Animals',
      '2 - Animals & Pet Supplies > Pet Supplies',
      '3 - Animals & Pet Supplies > Pet Supplies > Bird Supplies',
      '7385 - Animals & Pet Supplies > Pet Supplies > Bird Supplies > Bird Cage Accessories',
    ];
    </script>

    附录:访问我们的数据

    现在我们已经将数据加载到这个结构中,看起来遍历它会很尴尬。

    const data = {
      "Animals & Pet Supplies": {
        "id": "1",
        "Live Animals": {
          "id": "3237"
        },
        "Pet Supplies": {
          "id": "2",
          "Bird Supplies": {
            "id": "3",
            "Bird Cage Accessories": {
              "id": "7385"
            }
          }
        }
      }
    }
    

    这很可能不是我们能想到的最好的数据结构,如果我们需要,还可以选择重新处理它。

    不过,感谢Iterator protocol,我们现在可以塑造我们的数据,而无需(过多地)考虑如何访问它。

    根据 Iterator 协议使我们的数据“可迭代”很容易,并且允许我们使用 JavaScript 构造,例如 ... 扩展运算符或 for...of 循环:

    const iterate = o => (
      { ...o
      , [Symbol.iterator]() {
          const entries = Object.entries(o).filter(([k, v]) => k !== 'id');
          return {
            next() {
              if (entries.length === 0) return {done: true};
              const [name, {id}] = entries.pop();
              return {done: false, value: {id, name}};
            }
          };
        }
      }
    );
    

    在这个实现中,我们将在每次迭代时返回一个对象{id, name}

    让我们进入第一层:

    for (let obj of iterate(data)) {
      console.log(obj)
    }
    //=> { id: '1', name: 'Animals & Pet Supplies' }
    

    让我们进入第二层:

    for (let obj of iterate(data['Animals & Pet Supplies'])) {
      console.log(obj)
    }
    // { id: '2', name: 'Pet Supplies' }
    // { id: '3237', name: 'Live Animals' }
    

    或者我们可以使用...扩展运算符直接存储到数组中:

    const level2 = [...iterate(data['Animals & Pet Supplies'])];
    // [ { id: '2', name: 'Pet Supplies' }
    //   { id: '3237', name: 'Live Animals' } ]
    

    【讨论】:

    • 这真的很有帮助。我对输出 JSON 的结构有疑问。生成输出后,我无法遍历,因为我无法迭代对象键,因为“id”是一个键,子类别是每个键。您如何看待对象的第一级是{ "version": "2019-07-10", "categories": [&lt;categories&gt;]},然后是后续的{ "id": &lt;id&gt;, "name": &lt;name&gt;, "categories": [&lt;categories&gt;] }
    • @ZacharyRussellHeineman 我明白了。如帖子中所述,加载文件后的结构可以重新处理以最好地满足您的需求。我在我的帖子中添加了一个附录,描述了一个替代方案。希望对您有所帮助。
    • 这个问题最近再次浮出水面。这是一个非常好的答案!请参阅我的潜在简化。
    【解决方案2】:

    由于一个现已删除的答案,这个问题刚刚重新出现。我以前没见过。 customcommander 的出色回答给我留下了深刻的印象。我喜欢问题的分解和简单的辅助函数。

    但我认为我们可以编写一个简单的 nest 函数,它也可以完成 deepmerge 依赖项所做的工作,从而简化我们的代码。

    所以这里有一个类似的方法,有这样一个nest 函数。

    const splitBy = (sep) => (xs) =>
      xs .split (sep) .map (s => s .trim ())
    
    const nest = ([p, ...ps], v, o) => 
      p == undefined ? o : {... o, [p] : ps .length == 0 ? v : nest (ps, v, o [p] || {})}
    
    const convert = (lines) => lines 
      .split ('\n') 
      .filter (Boolean)
      .map (splitBy ('-'))
      .map (([id, desc]) => [id, splitBy ('>') (desc)])
      .reduce ((a, [id, path]) => nest ([... path, 'id'], id, a), {})
    
    
    const lines = `
    1 - Animals & Pet Supplies
    3237 - Animals & Pet Supplies > Live Animals
    2 - Animals & Pet Supplies > Pet Supplies
    3 - Animals & Pet Supplies > Pet Supplies > Bird Supplies
    7385 - Animals & Pet Supplies > Pet Supplies > Bird Supplies > Bird Cage Accessories`
    
    console .log (convert (lines))
    .as-console-wrapper {max-height: 100% !important; top: 0}

    这个版本的nest 相当简单。它没有deepmerge 或我更熟悉的Ramda 的assocPath 的复杂性(免责声明:我是Ramda 的作者。)但是对于这个问题已经足够了。

    【讨论】:

      猜你喜欢
      • 2021-05-19
      • 1970-01-01
      • 1970-01-01
      • 2019-11-12
      • 1970-01-01
      • 2020-07-14
      • 2020-10-11
      • 2018-06-26
      • 1970-01-01
      相关资源
      最近更新 更多