how to compose encoding/decoding params?
sibelius opened this issue · 2 comments
sibelius commented
My use case is to keep all filters/search options of a screen in query string
I'd like to declare complex encoding/decoding params to make sure all filters keys are parsed properly
example of date range param
import moment, { Moment } from 'moment';
import { QueryParamConfig, encodeObject, decodeObject } from 'use-query-params';
type DateRange = {
begin: Moment;
end: Moment;
};
export const DateRangeParam: QueryParamConfig<DateRange> = {
encode(dateRangeObj: { begin: Moment; end: Moment }): string | undefined {
if (dateRangeObj === null) {
return undefined;
}
if (!dateRangeObj.begin) {
return encodeObject({
end: dateRangeObj.end.toISOString(),
});
}
if (!dateRangeObj.end) {
return encodeObject({
begin: dateRangeObj.begin.toISOString(),
});
}
return encodeObject({
begin: dateRangeObj.begin.toISOString(),
end: dateRangeObj.end.toISOString(),
});
},
decode(input: string | string[] | null | undefined): Moment | undefined {
if (input == null || !input.length) {
return undefined;
}
const dateRangeObj = decodeObject(input);
return {
begin: dateRangeObj.begin ? moment(dateRangeObj.begin) : null,
end: dateRangeObj.end ? moment(dateRangeObj.end) : null,
};
},
};
example: date=begin-2020-01-01T03%3A00%3A00.000Z_end-2020-01-31T03%3A00%3A00.000Z
I'd like to easily to something like this:
ObjectParams<{begin: MomentParam, end: MomentParam>
similar to yup validation schema (https://github.com/jquense/yup)
sibelius commented
create a simpler Param builder
export const getSchemaParam = (objParam: QueryParamConfig<any>): QueryParamConfig<any> => {
return {
encode(input: object) {
const objEncoded = Object.keys(input).reduce((acc, key) => {
const value = input[key];
const param = objParam[key];
// is it a QueryParam?
if (param && param.encode && param.decode) {
return {
...acc,
[key]: param.encode(value),
};
}
return {
...acc,
[key]: value,
};
}, {});
return encodeObject(objEncoded);
},
decode(input: string | string[] | undefined): object | undefined {
if (input == null || !input.length) {
return undefined;
}
const obj = decodeObject(input);
return Object.keys(obj).reduce((acc, key) => {
const value = obj[key];
const param = objParam[key];
// is it a QueryParam?
if (param && param.encode && param.decode) {
return {
...acc,
[key]: param.decode(value),
};
}
return {
...acc,
[key]: value,
};
}, {});
},
};
};
const filterDefinition = {
begin: MomentParam,
end: MomentParam,
};
const FilterParam = getSchemaParam(filterDefinition);
FilterParam.encode
FilterParam.decode
it also needs to be recursive
sibelius commented
nesting solution
import { QueryParamConfig, encodeObject, decodeObject } from 'use-query-params';
const encodeObj = (objParam: QueryParamConfig<any>) => (input: object) => {
if (objParam && objParam.encode && objParam.decode) {
return objParam.encode(input);
}
const objEncoded = Object.keys(input).reduce((acc, key) => {
const value = input[key];
const param = objParam[key];
// is it a QueryParam?
if (param && param.encode && param.decode) {
return {
...acc,
[key]: param.encode(value),
};
}
// nested object param
if (typeof value === 'object') {
const encoded = encodeObj({
[key]: param,
})(value);
return {
...acc,
[key]: encoded,
};
}
return {
...acc,
[key]: value,
};
}, {});
return encodeObject(objEncoded);
};
const decodeObj = (objParam: QueryParamConfig<any>) => (input: string | string[] | undefined): object | undefined => {
if (input == null || !input.length) {
return undefined;
}
if (objParam && objParam.encode && objParam.decode) {
return objParam.decode(input);
}
const obj = decodeObject(input);
return Object.keys(obj).reduce((acc, key) => {
const value = obj[key];
const param = objParam[key];
// is it a QueryParam?
if (param && param.encode && param.decode) {
return {
...acc,
[key]: param.decode(value),
};
}
// nested object param
if (typeof param === 'object') {
const decoded = decodeObj(param)(value);
return {
...acc,
[key]: decoded,
};
}
return {
...acc,
[key]: value,
};
}, {});
};
// TODO - improve type
export const getSchemaParam = (objParam: QueryParamConfig<any>): QueryParamConfig<any> => {
const encode = encodeObj(objParam);
const decode = decodeObj(objParam);
return {
encode,
decode,
};
};
tests
import { StringParam, BooleanParam } from 'use-query-params';
import { getSchemaParam } from '../getSchemaParam';
const result = 'quadrant-atende_user-manager-managerId_oneOnOne-hasMadeOne-true';
// TODO - fix nesting
it.skip('should encode complex filter properly using schemaParam', () => {
const userFilterDefinition = {
quadrant: StringParam,
user: {
manager: StringParam,
oneOnOne: {
hasMadeOne: BooleanParam,
},
},
};
const UserFilterParam = getSchemaParam(userFilterDefinition);
const filters = {
quadrant: 'atende',
user: {
manager: 'managerId',
oneOnOne: {
hasMadeOne: true,
},
},
};
const encoded = UserFilterParam.encode(filters);
expect(encoded).toEqual(result);
});
// TODO - fix nesting
it.skip('should decode complex filter properly using schemaParam', () => {
const userFilterDefinition = {
quadrant: StringParam,
user: {
manager: StringParam,
oneOnOne: {
hasMadeOne: BooleanParam,
},
},
};
const UserFilterParam = getSchemaParam(userFilterDefinition);
const filters = {
quadrant: 'atende',
user: {
manager: 'managerId',
oneOnOne: {
hasMadeOne: true,
},
},
};
const encoded = UserFilterParam.decode(result);
expect(encoded).toEqual(filters);
});
tests are still failing