A
arktype3w ago
Kinsi

Parse / validate urlencoded object

When posting something like {foo[a]: 1, foo[b]: 2}, is it possible to constrain the keys to always have a string and the value to always be a number? In the same move, is it possible to convert that to a mapped object like qs would too? (foo: {a: 1, b: 2})
9 Replies
TizzySaurus
TizzySaurus3w ago
I guess you mean something like
type.Record(/^foo\[[\w+]\]$/, "number").pipe((ctx) => {
// loop over ctx.value and map accordingly, returning the {foo: {a: 1, b:2}}
});
type.Record(/^foo\[[\w+]\]$/, "number").pipe((ctx) => {
// loop over ctx.value and map accordingly, returning the {foo: {a: 1, b:2}}
});
?
Kinsi
KinsiOP3w ago
Ahh regex, that makes sense - Tho I dont think that would work for me since I need to validate additional known-keys as well. What I did now is use narrow - Not sure if that is ideal, if you know a better solution I'd appreciate that!
type({
"known": "number"
}).narrow((data, ctx) => {
const foo = {};

for(const key of Object.keys(data)) {
const m = key.match(/^foo\[(\d+)\]$/);

if(!m)
continue;

if(typeof data[key] === "string")
foo[m[1]] = data[key];

delete data[key];
}
//@ts-expect-error
data.foo = foo;
return true;
});
type({
"known": "number"
}).narrow((data, ctx) => {
const foo = {};

for(const key of Object.keys(data)) {
const m = key.match(/^foo\[(\d+)\]$/);

if(!m)
continue;

if(typeof data[key] === "string")
foo[m[1]] = data[key];

delete data[key];
}
//@ts-expect-error
data.foo = foo;
return true;
});
TizzySaurus
TizzySaurus3w ago
Something like
type({
[/yourRegex/]: "number",
known: "number"
}).pipe(...)
type({
[/yourRegex/]: "number",
known: "number"
}).pipe(...)
should work I don't remember the exact syntax for in-line regex keys but it is possible You should use .pipe not .narrow btw narrow is intended for validation checks, whereas pipe is to actually change the value of the output
Kinsi
KinsiOP3w ago
Gotcha, switched to pipe instead thanks - I couldnt find anything like your example in the docs tho (regexp as key) fwiw Ultimately I've ended up with this now (Added it as an optional field to the main validator so that its typed / known):
#update_validator = type({
known: "number",
"foo[]?": "string[] == 2"
}).pipe(data => {
const foo: any[] = [];

for(const key of Object.keys(data)) {
const m = key.match(/^foo\[(\d+)\]$/);

if(!m)
continue;

if(data[key])
foo.push([Number(m[1]), data[key]]);

delete data[key];
}
data["foo[]"] = foo;
return data;
});
#update_validator = type({
known: "number",
"foo[]?": "string[] == 2"
}).pipe(data => {
const foo: any[] = [];

for(const key of Object.keys(data)) {
const m = key.match(/^foo\[(\d+)\]$/);

if(!m)
continue;

if(data[key])
foo.push([Number(m[1]), data[key]]);

delete data[key];
}
data["foo[]"] = foo;
return data;
});
TizzySaurus
TizzySaurus3w ago
I don't think that's right. .pipe takes a ctx, that has a data property. So at a minimum you'd need ({data}) => {...}
Kinsi
KinsiOP3w ago
It does work for me 🤷‍♂️
TizzySaurus
TizzySaurus3w ago
https://arktype.io/docs/objects#properties-index Looks like it might be {"[/[a-z]+/]": "string"} for regex index signatures
ArkType
ArkType Docs
TypeScript's 1:1 validator, optimized from editor to runtime
TizzySaurus
TizzySaurus3w ago
Ah, I'm wrong, it takes (data, ctx). My bad Otherwise foo should technically be unknown[] by best practice, and I would prefer Object.keys(data).forEach(key => {...}) over for (const key of Object.keys(data)) {...} but that's personal preference In fact, you should be able to type foo as [string, number][] or whatever it is
Kinsi
KinsiOP3w ago
I'll have a look, thank you!

Did you find this page helpful?