【问题标题】:Generic way for filtering properties from arbitrary and deeply nested JSON (with arrays)从任意和深度嵌套的 JSON(带数组)中过滤属性的通用方法
【发布时间】:2021-04-03 00:43:46
【问题描述】:

目标

我想在 TypeScript 中开发一个中间件,用于过滤 REST API 的响应并仅返回已定义的属性。 它应该通用,即独立于特定实体。它们的属性和确切的深度(例如,具有任意数量的关系)都不一定是已知的。

示例

一个作者有任意数量的文章和任意数量的 cmets。

[
    {
        "name": "John Doe",
        "email": "john@doe.com",
        "articles": [
            {
                "title": "Lalilu 1",
                "text:": "la li lu",
                "comments": [
                    {
                        "author": "Bendthatdict Cumberstone",
                        "text": "Great article!"
                    },
                    {
                        "author": "Bendthatdict Cumberstone",
                        "text": "Great article!"
                    }
                ]
            },
            {
                "title": "Lalilu 1",
                "text:": "la li lu",
                "comments": [
                    {
                        "author": "Bendthatdict Cumberstone",
                        "text": "Great article!"
                    },
                    {
                        "author": "Bendthatdict Cumberstone",
                        "text": "Great article!"
                    }
                ]
            }
        ]
    },
    {
        "name": "Jane Doe",
        "email": "jane@doe.com",
        "articles": [
            {
                "title": "Lalilu 1",
                "text:": "la li lu",
                "comments": [
                    {
                        "author": "Bendthatdict Cumberstone",
                        "text": "Great article!"
                    },
                    {
                        "author": "Bendthatdict Cumberstone",
                        "text": "Great article!"
                    }
                ]
            },
            {
                "title": "Lalilu 1",
                "text:": "la li lu",
                "comments": [
                    {
                        "author": "Bendthatdict Cumberstone",
                        "text": "Great article!"
                    },
                    {
                        "author": "Bendthatdict Cumberstone",
                        "text": "Great article!"
                    }
                ]
            }
        ]
    }
]

现在我想指定它应该返回除每篇文章的“文本”和每条评论的“作者”之外的所有内容。

使用 glob 表示法的语法可能如下所示:

select("*,!articles.text,!articles.comments.author")

方法

对于对象和嵌套对象来说非常简单,例如使用“lodash”的pick()和omit(),但是当数组进入游戏时我失败了。 我做了一些研究,发现了 json-masknode-globglob-object 等软件包,但它们都不能完全满足我的需求,我无法将它们组合起来取得成功。

问题

一般过滤任意嵌套的 JSON 与任意数量的其他对象/数组的最有效方法是什么? 另外,TypeScripts 类型系统如何发挥优势?

我将非常感谢通用编码方法,甚至是已经可以做到这一点的包的提示!

【问题讨论】:

  • 可能您需要坐下来为此编写一个漂亮的递归函数,该函数会检查嵌套属性并相应地调用数组、对象递归函数,看不到打字稿可以提供帮助的方法明确的。

标签: javascript json typescript data-structures filter


【解决方案1】:

简而言之,我会将其分解为函数。您可以创建帮助程序,使用字符串/过滤器或多或少地执行您想要的操作,但我会反过来进行操作。获得一种很好的迭代方式,以便可以完成任何后期处理,然后根据您的意愿构建您的助手。这就是我的意思:

示例



export interface IComment {
  author: string;
  text: string;
}

export interface IArticle {
  title: string;
  text: string;
  comments: IComment[];
}

export interface IComposer {
  name: string,
  email: string,
  articles: IArticle[];
}

// Remove items from list for brevity sake...
const authorList = [
  {
    "name": "John Doe",
    "email": "john@doe.com",
    "articles": [
      {
        "title": "Lalilu 1",
        "text": "la li lu",
        "comments": [
          {
            "author": "Bendthatdict Cumberstone",
            "text": "Great article!"
          }
        ]
      }
    ]
  }
] as IComposer[];


/**
 * Accepts JSON string or array of type.
 *
 * @param arr a JSON string containing array of type or array of type.
 */
export function selectFrom<T extends Record<string, any>>(arr: string | T[]) {

  // If you want to use this route I would suggest
  // also a function to validate that the JSON is
  // shaped correctly.
  if (typeof arr === 'string')
    arr = JSON.parse(arr);

  const collection = arr as T[];

  const api = {
    filters: [],
    register,
    run
  };

  /**
   * Register a search op.
   * @param fn function returning whether or not to filter the result.
   */
  function register(fn: (obj: T) => Partial<T>) {
    if (typeof fn === 'function')
      api.filters.push(fn);
    return api;
  }

  /**
   * Run registered ops and filter results.
   */
  function run() {

    return collection.reduce((results, obj) => {

      let result = obj;

      // Don't use reducer here as you can't break
      // and would unnecessarily loop through filters
      // that have no need to run, use for of instead.
      for (const filter of api.filters) {

        // if we set the result to null 
        // don't continue to run filters.
        if (!result) break;

        // Pipe in the previous result, we start with
        // original object but it's shape could change
        // so we keep iterating with the previous result.
        const filtered = filter(result);

        // update the result.
        if (filtered)
          result = filtered;

      }


      if (result)
        results.push(result);

      return results;

      // If changing the object you're going to 
      // end up with partials of the original
      // shape or interface.

    }, [] as Partial<T>[]);

  }

  return api;

}

用法

通过使这个函数基于核心,您可以获得更多的灵活性。从那里您可以创建一个简单的助手,将您的 Glob 或类似 SQL 的字符串映射到预定义的过滤器函数。如果您还有其他问题,请告诉我。


const filtered = 
  selectFrom(authorList)
    .register((composer) => {

      composer.articles = composer.articles.map(article => {

        const { text, ...filteredArticle } = article;

        filteredArticle.comments = filteredArticle.comments.map(comment => {

          const { author, ...filteredComment } = comment;

          return filteredComment as typeof comment;

        });

        // Note setting to type of IArticle here so typescript
        // doesn't complain, this is because you are removing props
        // above so the shape changes so you may want to consider
        // setting the props you plan to strip as optional or make
        // everything a partial etc. I'll leave that to you to decide.
        return filteredArticle as typeof article;

      });

      return composer;

    })
    .run();

接下来是什么

从这里得到你想要的关于字符串解析的地方。请记住,Lodash 确实支持深入到数组中的嵌套值。你可以看到这个here in the docs

鉴于您可以同时使用 _.get _.omit... 等以及使用点符号进行一点解析来利用 Lodash。

用权限做这件事。因此,我强烈认为您需要从一个简单的 api 开始进行处理,然后从那里将您的地图从 Glob 或 SQL 字符串映射到这些帮助程序。

【讨论】:

  • 哇,感谢您提供详细而有创意的答案!我花了一点时间才理解这一点,这似乎是一个非常明智和可行的方法——我想我会这样尝试。谢谢!
  • @stoniemahonie 如果您有任何问题或遇到困难,我们会很乐意为您提供帮助。做了相当多的这类事情,有很多方法可以在这里给猫剥皮,但多年来坚信分手更容易处理。否则我会做一些疯狂的废话,试图在一些光滑的单次迭代中完成所有工作,并且无法弄清楚两年后我到底做了什么哈哈。
猜你喜欢
  • 2021-04-02
  • 1970-01-01
  • 1970-01-01
  • 2019-01-29
  • 2016-06-11
  • 1970-01-01
  • 2014-06-10
  • 1970-01-01
  • 2023-03-05
相关资源
最近更新 更多