如何使用 TypeScript 构建灵活的可扩展过滤器?
How to form flexible expandable filter with TypeScript?
我需要使用运算符 'OR' 和 'AND' 使用 TypeScript 构建一个通用过滤器。也应该有一种使用范围和多选过滤器的方法。
但是我是 OOP 的新手,无法理解我应该如何使用 classes 来完成它。
对后端的过滤器请求应该看起来像这样,如果有新类型,就必须有一种方法来扩展它们:
[
{ field: 'id', operator: '=', value: 12 },
'OR',
{ field: 'name', operator: '=', value: 'testName' },
'OR',
[
{ field: 'age', operator: '>=', value: '20' },
'AND',
{ field: 'age', operator: '=<', value: '30' }
]
]
我形成了一个看起来像这样的对象数组,并提供给形成过滤器的 class:
[
{
field: 'id',
type: 'default',
value: 12
},
{
field: 'id2',
type: 'default',
value: 13
},
{
field: 'name',
type: 'default',
value: 'yulya'
},
{
field: 'age',
type: 'range',
value: '20 - 30'
}
]
class FilterParameters 看起来像这样,但是它不可扩展:
export class FilterParameters {
filter: (string | object | number)[]
constructor() {
this.filter = []
}
createFilter(receivedFields: (object)[]) {
let filterArr:(object)[] = []
let defaultFilter = receivedFields.filter((item) => item.type === 'default')
let rangeFilter = receivedFields.filter(item => item.type === 'range')
defaultFilter.forEach((item:any) => {
let defaultFilterObj = {
field: item.field,
operator: '=',
value: item.value
}
filterArr.push(defaultFilterObj)
})
rangeFilter.forEach((item:any) => {
let conditionArr = []
let startCondition = item.value.split(' ')[0]
let endCondition = item.value.split(' ').pop()
let objStartCondition = {
field: item.field,
operator: '>=',
value: startCondition
}
let objEndCondition = {
field: item.field,
operator: '=<',
value: endCondition
}
conditionArr.push(objStartCondition)
conditionArr.push('AND')
conditionArr.push(objEndCondition)
filterArr.push(conditionArr)
})
this.filter = filterArr.map((e, i) => i < filterArr.length - 1 ? [e, 'OR'] : [e]).reduce((a, b) => a.concat(b))
return this.filter
}
}
如何创建一个 class 形成过滤器并易于扩展?
如果我从头开始设计它,我会更改很多东西,因为您当前的方法有很多限制。例如,输入数组总是使用 'OR'
而无法使用 'AND'
或创建分组。
我假设您的输入格式和输出格式是固定的,我们的任务只是创建一种更可扩展的方式来从输入映射到输出。我做的第一件事是定义这些类型。
输入的每个过滤器看起来像这样,我们希望收到它们的数组。
interface InputFilter {
field: string;
type: string;
value: any;
}
由于嵌套,输出更复杂。
interface OutputFilterElement {
field: string;
operator: string;
value: any;
}
type Joiner = 'OR' | 'AND';
type OutputFilterArray = Array<OutputFilterElement | Joiner | OutputFilterArray>
type OutputFilter = OutputFilterElement | OutputFilterArray;
我们希望解析成为一个可扩展的系统,允许添加新的输入类型并将它们映射到它们的输出。如果我们要添加一个新类型,我们需要什么信息?我们需要知道它匹配的 type
字符串以及它如何将这种类型的输入对象映射到输出。我们可以把它写成 interface
.
interface TypeParser {
type: string;
parse: (input: InputFilter) => OutputFilter;
}
我们希望 FilterParameters
class 只知道 TypeParser
接口,而不知道或不关心该接口的具体实现。所以我们可以通过将任意数量的 TypeParser
对象作为参数传递给 constructor
.
来实例化这个 class
class 有一个 public 方法 parse
来根据其可用的类型解析器解析一组输入过滤器。它寻找匹配的解析器,然后委托实际解析。
export class FilterParameters {
private readonly typeParsers: TypeParser[];
/**
* construct an isntance of the FilterParser by passing the TypeParsers to handle specific types
*/
constructor(...typeParsers: TypeParser[]) {
this.typeParsers = typeParsers;
}
/**
* helper function parses each element of the array
* might throw an error
*/
private parseOne(input: InputFilter): OutputFilter {
// find the parser for this type
const parser = this.typeParsers.find(p => p.type === input.type);
if (!parser) {
throw new Error(`no filter found for type ${input.type}`);
}
return parser.parse(input);
}
/**
* handle an array of individual filters by assuming an 'OR' relationship
*/
public parse(input: InputFilter[]): OutputFilter {
// use flatMap to insert 'OR' between elements
return input.flatMap(
(condition, i) => {
const parsed = this.parseOne(condition);
return i === 0 ? [parsed] : ['OR', parsed]
}
);
// do we want to catch errors here? or allow them to be thrown?
}
}
如果不需要任何类型的实例,我认为使用 classes 是没有意义的,所以我正在创建 TypeParser
的具体实现作为实现接口的对象.
const EqualityParser: TypeParser = {
type: 'default',
parse: ({ field, value }: InputFilter): OutputFilter => {
return ({
field,
// operator is always equals for default filter
operator: '=',
value
});
}
}
const RangeParser: TypeParser = {
type: 'range',
parse: ({ field, value }: InputFilter): OutputFilter => {
// split based on hyphen with optional spaces before and after
const [min, max] = value.split(/\s*-\s*/);
// are these always numbers? should we use parseFloat?
return ([{
field,
operator: '>=',
value: min
},
'AND',
{
field,
operator: '<=',
value: max
}]);
}
}
为了使用这两个解析器解析您的输入,您可以这样写:
const filterer = new FilterParameters(EqualityParser, RangeParser);
const output = filterer.parse(input);
我需要使用运算符 'OR' 和 'AND' 使用 TypeScript 构建一个通用过滤器。也应该有一种使用范围和多选过滤器的方法。 但是我是 OOP 的新手,无法理解我应该如何使用 classes 来完成它。 对后端的过滤器请求应该看起来像这样,如果有新类型,就必须有一种方法来扩展它们:
[
{ field: 'id', operator: '=', value: 12 },
'OR',
{ field: 'name', operator: '=', value: 'testName' },
'OR',
[
{ field: 'age', operator: '>=', value: '20' },
'AND',
{ field: 'age', operator: '=<', value: '30' }
]
]
我形成了一个看起来像这样的对象数组,并提供给形成过滤器的 class:
[
{
field: 'id',
type: 'default',
value: 12
},
{
field: 'id2',
type: 'default',
value: 13
},
{
field: 'name',
type: 'default',
value: 'yulya'
},
{
field: 'age',
type: 'range',
value: '20 - 30'
}
]
class FilterParameters 看起来像这样,但是它不可扩展:
export class FilterParameters {
filter: (string | object | number)[]
constructor() {
this.filter = []
}
createFilter(receivedFields: (object)[]) {
let filterArr:(object)[] = []
let defaultFilter = receivedFields.filter((item) => item.type === 'default')
let rangeFilter = receivedFields.filter(item => item.type === 'range')
defaultFilter.forEach((item:any) => {
let defaultFilterObj = {
field: item.field,
operator: '=',
value: item.value
}
filterArr.push(defaultFilterObj)
})
rangeFilter.forEach((item:any) => {
let conditionArr = []
let startCondition = item.value.split(' ')[0]
let endCondition = item.value.split(' ').pop()
let objStartCondition = {
field: item.field,
operator: '>=',
value: startCondition
}
let objEndCondition = {
field: item.field,
operator: '=<',
value: endCondition
}
conditionArr.push(objStartCondition)
conditionArr.push('AND')
conditionArr.push(objEndCondition)
filterArr.push(conditionArr)
})
this.filter = filterArr.map((e, i) => i < filterArr.length - 1 ? [e, 'OR'] : [e]).reduce((a, b) => a.concat(b))
return this.filter
}
}
如何创建一个 class 形成过滤器并易于扩展?
如果我从头开始设计它,我会更改很多东西,因为您当前的方法有很多限制。例如,输入数组总是使用 'OR'
而无法使用 'AND'
或创建分组。
我假设您的输入格式和输出格式是固定的,我们的任务只是创建一种更可扩展的方式来从输入映射到输出。我做的第一件事是定义这些类型。
输入的每个过滤器看起来像这样,我们希望收到它们的数组。
interface InputFilter {
field: string;
type: string;
value: any;
}
由于嵌套,输出更复杂。
interface OutputFilterElement {
field: string;
operator: string;
value: any;
}
type Joiner = 'OR' | 'AND';
type OutputFilterArray = Array<OutputFilterElement | Joiner | OutputFilterArray>
type OutputFilter = OutputFilterElement | OutputFilterArray;
我们希望解析成为一个可扩展的系统,允许添加新的输入类型并将它们映射到它们的输出。如果我们要添加一个新类型,我们需要什么信息?我们需要知道它匹配的 type
字符串以及它如何将这种类型的输入对象映射到输出。我们可以把它写成 interface
.
interface TypeParser {
type: string;
parse: (input: InputFilter) => OutputFilter;
}
我们希望 FilterParameters
class 只知道 TypeParser
接口,而不知道或不关心该接口的具体实现。所以我们可以通过将任意数量的 TypeParser
对象作为参数传递给 constructor
.
class 有一个 public 方法 parse
来根据其可用的类型解析器解析一组输入过滤器。它寻找匹配的解析器,然后委托实际解析。
export class FilterParameters {
private readonly typeParsers: TypeParser[];
/**
* construct an isntance of the FilterParser by passing the TypeParsers to handle specific types
*/
constructor(...typeParsers: TypeParser[]) {
this.typeParsers = typeParsers;
}
/**
* helper function parses each element of the array
* might throw an error
*/
private parseOne(input: InputFilter): OutputFilter {
// find the parser for this type
const parser = this.typeParsers.find(p => p.type === input.type);
if (!parser) {
throw new Error(`no filter found for type ${input.type}`);
}
return parser.parse(input);
}
/**
* handle an array of individual filters by assuming an 'OR' relationship
*/
public parse(input: InputFilter[]): OutputFilter {
// use flatMap to insert 'OR' between elements
return input.flatMap(
(condition, i) => {
const parsed = this.parseOne(condition);
return i === 0 ? [parsed] : ['OR', parsed]
}
);
// do we want to catch errors here? or allow them to be thrown?
}
}
如果不需要任何类型的实例,我认为使用 classes 是没有意义的,所以我正在创建 TypeParser
的具体实现作为实现接口的对象.
const EqualityParser: TypeParser = {
type: 'default',
parse: ({ field, value }: InputFilter): OutputFilter => {
return ({
field,
// operator is always equals for default filter
operator: '=',
value
});
}
}
const RangeParser: TypeParser = {
type: 'range',
parse: ({ field, value }: InputFilter): OutputFilter => {
// split based on hyphen with optional spaces before and after
const [min, max] = value.split(/\s*-\s*/);
// are these always numbers? should we use parseFloat?
return ([{
field,
operator: '>=',
value: min
},
'AND',
{
field,
operator: '<=',
value: max
}]);
}
}
为了使用这两个解析器解析您的输入,您可以这样写:
const filterer = new FilterParameters(EqualityParser, RangeParser);
const output = filterer.parse(input);