I am trying to lookup the value of a key with fp-ts
. The key might not be present.
type Data = {
a?: number;
b?: string;
c?: { e?: string; w: number };
};
const e = R.lookup('b')({ a: 23, b: 'asdfasdf', c: { e: 'asdf', w: 23}} as Data)
I thought the type of e
inferred by Typescript would be Option<string>
but it is:
O.Option<string | number | {
e?: string | undefined;
w: number;
}>
This looks like all the possible types in Data
. Is this intended behaviour? And how shall I narrow my types to only the "potential" type of b
, so that I can continue a pipeline from an Option<string>
.
I tried the approach below, which marks e
as Option<string>
but then flags the entire object {a: 23...
as "not assignable to parameter of type 'Record<string, string>'"
const getB = R.lookup("b")
const e = getB<string>({ a: 234, b: "asdfasdf", c: { e: "asdf", w: 23 } } as Data);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// Argument of type 'Data' is not assignable to parameter of type 'Record<string, string>'.
Property 'a' is incompatible with index signature.
Type 'number' is not assignable to type 'string'.
I think R.lookup
's types just misalign with what you're hoping for. The problem is that R.lookup
does not keep track of the fact that you're expecting 'b'
to be a keyof Data
. It just knows that b
is a string
and the function it returns has the signature:
<A>(record: Record<string, A>) => Option<A>
So it looks at data as a Record
and the type for A becomes Data[keyof Data]
which results in the type you're seeing.
I think this mainly just stems from wanting to use a Record
in a way it's not really intended to be used. A Record
has a uniform type for any key you hand into it. In order to get a uniform type, the fp-ts
code has to infer that at each key there might be the union type of all the values in data.
If instead, you know that you have a Data
object, you can write code like:
import * as O from 'fp-ts/Option';
const data: Data = { a: 234, b: "asdfasdf", c: { e: "asdf", w: 23 } };
const maybeB: Option<string> = O.fromNullable(data.b);
If instead, you don't actually know that you'll have Data
because your object is actually unknown
, rather than casting it to Data
you could use a library (like io-ts
since you're already using fp-ts
) to first validate the shape of the data. For example
import * as E from 'fp-ts/Either';
import * as O from 'fp-ts/Option';
import { flow } from 'fp-ts/function';
import * as t from 'io-ts';
const dataSchema = t.partial({
a: t.number,
b: t.string,
c: t.type({
e: t.union([t.string, t.undefined]),
w: t.number,
}),
});
// This will be equivalent to your data definition but can be inferred
// from the schema definition
type Data = t.TypeOf<typeof dataSchema>;
// The explicit type here is unnecessary but just to illustrate
const getB: (input: unknown) => O.Option<string> = flow(
dataSchema.decode, // -> Either<t.Errors, Data>
O.fromEither, // -> Option<Data>
O.chain((d) => O.fromNullable(d.b)) // Option<string>
);
console.log(getB(data)); // { _tag: 'Some', value: 'asdfasdf' }
As per other comments, possibly the ideal approach to this would be using io-ts
for monocle-ts
. But seeing you seem interested in how fp-ts
could handle it itself - for just a single field - I offer you this.
The gist is, that once you have got yourself the optional, you simply need to use typescript type narrowing. This can be done with fp-ts
various fromPredicate
with the overloaded form using a Refinement
. And for a lot of basic types (like string
) fp-ts
provide some out of the box - such as S.isString
below.
import { pipe } from "fp-ts/function";
import * as O from "fp-ts/Option";
import * as R from "fp-ts/Record";
import * as S from "fp-ts/string";
type Data = {
a?: number;
b?: string;
c?: { e?: string; w: number };
};
const testData: Data = { a: 23, b: "asdfasdf", c: { e: "asdf", w: 23 } };
const b = pipe(
testData,
R.lookup("b"),
O.chain(
O.fromPredicate(S.isString)
)
);
But although that gets you by with the single field, anything more that that is going to require more and more boilerplate, so then you might as well use io-ts
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.