简体   繁体   English

从任意和深度嵌套的 JSON (带数组)中过滤属性的通用方法

[英]Generic way for filtering properties from arbitrary and deeply nested JSON (with arrays)

Goal目标

I want to develop a middleware in TypeScript that filters the response of a REST API and returns only defined properties.我想在 TypeScript 中开发一个中间件,它过滤 REST API 的响应并仅返回定义的属性。 It should work generically, ie independent of specific entities.它应该通用,即独立于特定实体。 Neither their properties nor the exact depth (eg with any number of relations) should be necessarily known.它们的属性和确切的深度(例如,具有任意数量的关系)都不一定是已知的。

Example例子

An author has any number of articles with any number of comments.一个作者有任意数量的文章和任意数量的评论。

[
    {
        "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!"
                    }
                ]
            }
        ]
    }
]

Now I want to specify that it should return everything except the "text" of each article and the "author" of each comment.现在我想指定它应该返回除每篇文章的“文本”和每条评论的“作者”之外的所有内容。

Syntax could look like this with glob notation:使用 glob 表示法的语法可能如下所示:

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

Approach方法

For objects and nested objects it is quite simple, eg with pick() and omit() of "lodash", but I fail when arrays step into the game.对于对象和嵌套对象,它非常简单,例如使用“lodash”的 pick() 和 omit(),但是当 arrays 进入游戏时我失败了。 I did some research and came across packages such as json-mask , node-glob or glob-object but none of them exactly met my needs and I was not able to combine them for success.我做了一些研究,遇到了诸如json-masknode-globglob-object之类的包,但它们都没有完全满足我的需求,我无法将它们组合起来取得成功。

Question问题

What is the most efficient way to generically filter an arbitrarily nested JSON with any number of further objects / arrays?用任意数量的其他对象 / arrays 一般过滤任意嵌套的 JSON 的最有效方法是什么? Also, how could the TypeScripts type system be used to advantage?此外,TypeScripts 类型系统如何发挥优势?

I would be very grateful for general coding approaches or even tips for a package that can already do this!对于已经可以做到这一点的 package 的一般编码方法甚至提示,我将非常感激!

In short I would break this up into functions.简而言之,我会将其分解为功能。 You could create helpers that do more or less what you want with a string/filter as you show however I'd work it in reverse.您可以创建帮助程序,使用字符串/过滤器或多或少地执行您想要的操作,但我会反过来进行操作。 Get a nice way to iterate so any post processing can be done, then build your helpers as you wish against that.获得一种很好的迭代方式,以便可以完成任何后期处理,然后根据您的意愿构建您的助手。 Here's what I mean:这就是我的意思:

Example例子



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;

}

Usage用法

By making this function based at the core you have a lot more flexibility.通过使这个 function 基于核心,您可以获得更多的灵活性。 From there you could make a simple helper that maps your Glob or SQL like string to the pre-defined filter functions.从那里您可以制作一个简单的助手,将您的 Glob 或 SQL 之类的字符串映射到预定义的过滤器函数。 Let me know if you have further questions.如果您还有其他问题,请告诉我。


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();

What's Next下一步是什么

From here to get where you want it's about string parsing.从这里得到你想要的关于字符串解析的地方。 Keep in mind Lodash does support gets down into nested values in an array.请记住,Lodash 确实支持深入到数组中的嵌套值。 You can see this here in the docs .你可以在文档中看到这个。

Given that you could leverage Lodash using both _.get _.omit... etc along with a little parsing using dot notation.鉴于您可以同时使用_.get _.omit...等以及使用点符号进行一些解析来利用 Lodash。

Done this very thing with permissions.使用权限完成了这件事。 As such I feel strongly you need to start with a simple api to process then from there make your map from either Glob like or SQL string to those helpers.因此,我强烈认为您需要从一个简单的 api 开始进行处理,然后从那里使您的 map 从 Glob 或 SQL 字符串到这些助手。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM