使用 fp-ts 和 io-ts 获取和映射数据
Fetching and mapping data with fp-ts and io-ts
我正在努力将获取的数据“按摩”成我想要的形状,使用 fp-ts
进行功能转换并使用 io-ts
进行数据验证。
我在找什么
我想要 getSchools()
到 return 一个 Error
描述出了什么问题,或者一组经过验证的 School
。我的代码有些工作,但问题是,如果获取的学校数组中的一所学校未通过验证,则一切都会失败。我只想过滤掉失败的那些,return 剩下的。
我目前的代码
/**
* API route for all Schools
*/
export default async (_: NextApiRequest, res: NextApiResponse<unknown>) => {
return new Promise(
pipe(
getSchools(),
fold(
(e) => of(res.status(400).end(e.message)),
(v) => of(res.status(200).json(v))
)
)
);
};
/**
* Handler for fetching Schools
*/
export function getSchools(): TaskEither<Error, Array<School>> {
return pipe(
fetch(schoolQuery(schoolQueryBody)),
chain(mapToschools),
chain(decode(t.array(School)))
);
}
function mapToschools(
inputs: Array<any>
): TaskEither<Error, Array<School>> {
try {
return right(inputs.map(mapToschool));
} catch (e) {
return left(new Error("Could not map input to school"));
}
}
export function mapToschool(input: any): School // Can throw Error
export const schoolQueryBody = `...`;
function fetch(query: string): TaskEither<Error, unknown>
export function decodeError(e: t.Errors): Error {
const missingKeys = e.map((e) => e.context.map(({ key }) => key).join("."));
return new Error(`Missing keys: ${missingKeys}`);
}
export const decode = <I, A>(Type: t.Decoder<I, A>) => (
res: I
): TaskEither<Error, A> => {
return pipe(fromEither(Type.decode(res)), mapLeft(decodeError));
};
我认为您在这里有一些选择 return,并且我认为 fp-ts
/ io-ts
的默认行为并不完全符合你想要什么。
发生了什么事
当您解析 t.array
时,只要其中一个值无法解码,您就会失败。在我看来,您想单独尝试解码每个值而不是使用 t.array
.
建议
我想我会这样说:
import { pipe } from 'fp-ts/lib/function';
import * as ArrayFP from 'fp-ts/lib/Array';
const undecodedSchools: unknown[] = [/* ... school response */];
const schools: School[] = pipe(
undecodedSchools,
ArrayFP.map(schoolCodec.decode), // Array<Either<t.Errors, School>>
ArrayFP.rights, // Takes an Array<Either<E, A>> -> Array<A>
);
这完全摆脱了可能不是您想要的 Either
。如果您想查看错误,不妨改为说:
const schools: {
left: Error[],
right: School[],
} = pipe(
undecodedSchools,
ArrayFP.map(schoolCodec.decode), // Array<Either<t.Errors, School>>
ArrayFP.separate, // Takes an Array<Either<A, B>> -> Separated<A[], B[]>
);
这从 eithers 数组中分离出两种类型的事物。
我正在努力将获取的数据“按摩”成我想要的形状,使用 fp-ts
进行功能转换并使用 io-ts
进行数据验证。
我在找什么
我想要 getSchools()
到 return 一个 Error
描述出了什么问题,或者一组经过验证的 School
。我的代码有些工作,但问题是,如果获取的学校数组中的一所学校未通过验证,则一切都会失败。我只想过滤掉失败的那些,return 剩下的。
我目前的代码
/**
* API route for all Schools
*/
export default async (_: NextApiRequest, res: NextApiResponse<unknown>) => {
return new Promise(
pipe(
getSchools(),
fold(
(e) => of(res.status(400).end(e.message)),
(v) => of(res.status(200).json(v))
)
)
);
};
/**
* Handler for fetching Schools
*/
export function getSchools(): TaskEither<Error, Array<School>> {
return pipe(
fetch(schoolQuery(schoolQueryBody)),
chain(mapToschools),
chain(decode(t.array(School)))
);
}
function mapToschools(
inputs: Array<any>
): TaskEither<Error, Array<School>> {
try {
return right(inputs.map(mapToschool));
} catch (e) {
return left(new Error("Could not map input to school"));
}
}
export function mapToschool(input: any): School // Can throw Error
export const schoolQueryBody = `...`;
function fetch(query: string): TaskEither<Error, unknown>
export function decodeError(e: t.Errors): Error {
const missingKeys = e.map((e) => e.context.map(({ key }) => key).join("."));
return new Error(`Missing keys: ${missingKeys}`);
}
export const decode = <I, A>(Type: t.Decoder<I, A>) => (
res: I
): TaskEither<Error, A> => {
return pipe(fromEither(Type.decode(res)), mapLeft(decodeError));
};
我认为您在这里有一些选择 return,并且我认为 fp-ts
/ io-ts
的默认行为并不完全符合你想要什么。
发生了什么事
当您解析 t.array
时,只要其中一个值无法解码,您就会失败。在我看来,您想单独尝试解码每个值而不是使用 t.array
.
建议
我想我会这样说:
import { pipe } from 'fp-ts/lib/function';
import * as ArrayFP from 'fp-ts/lib/Array';
const undecodedSchools: unknown[] = [/* ... school response */];
const schools: School[] = pipe(
undecodedSchools,
ArrayFP.map(schoolCodec.decode), // Array<Either<t.Errors, School>>
ArrayFP.rights, // Takes an Array<Either<E, A>> -> Array<A>
);
这完全摆脱了可能不是您想要的 Either
。如果您想查看错误,不妨改为说:
const schools: {
left: Error[],
right: School[],
} = pipe(
undecodedSchools,
ArrayFP.map(schoolCodec.decode), // Array<Either<t.Errors, School>>
ArrayFP.separate, // Takes an Array<Either<A, B>> -> Separated<A[], B[]>
);
这从 eithers 数组中分离出两种类型的事物。