Open kmbt opened 2 years ago
After giving it some additional thought, I've managed to rewrite my solution in a much cleaner manner by simply relying on the Option
type instead of using undefined
.
The optional
combinator replaces the above collapse
and may be found useful by itself - it turns a decoder into one that always succeeds (Right
) but returns its result as an Option
- Some<A>
for success and None
for failure. This might be useful in general for parts of a decoder that are expected to fail sometimes but should be prevented from making the whole decoder fail - an optional part of a pattern so to speak.
import * as A from 'fp-ts/Array'
import * as D from 'io-ts/Decoder'
import * as O from 'fp-ts/Option'
import { flow } from 'fp-ts/lib/function';
const optional = <I, A>(d: D.Decoder<I, A>): D.Decoder<I, O.Option<A>> => ({
decode: flow(
d.decode,
O.fromEither,
D.success
)
})
const filteredArray: <A>(d: D.Decoder<unknown, A>) => D.Decoder<unknown, Array<A>> = flow(
optional,
D.array,
D.parse(flow(
A.filterMap(identity),
D.success
)))
Can your requirement also be expressed as a computation that can partially fail? If so, another approach would be to use a These
instead of an Either
, where you would treat Both
as a warning.
This way the decoding errors of failed array elements would not vanish into thin air. Also it would allow you, if needed, to distinguish between exceptions and warnings during decoding.
E. g.
Array where all elements can be decoded > E.right(as)
Exception while decoding array elements > E.left(e)
Array that can be partially decoded > TH.both(e, as)
I am also looking for this feature as well. Is there a similar way this could be accomplished using the stable api instead of using Decoder?
I took a stab at implementing this. This seems to work, however, I want to log the failures and if I have a union type it'll log failures for the unions it wasn't able to decode, but the final object passes as is the correct type.
export function validItemsOnlyArray<C extends t.Mixed>(theType: C) {
return withValidate(t.array(theType), (itemToDecode, context) => {
return pipe(
// validate that the value is at least an array
// if it passes, then chain and validate each item
t.UnknownArray.validate(itemToDecode, context),
E.chain((validArrayObject) => {
const decoded = pipe(
validArrayObject,
A.map((arrayItem) => {
const decodeResult = theType.decode(arrayItem)
//TODO: figure out how to log errors, currently logging for each failed union type
return isRight(decodeResult)
? t.success(decodeResult.right)
: t.failure(arrayItem, context)
}),
A.separate,
)
return t.success(decoded.right)
}),
)
})
}
🚀 Feature request
Current Behavior
There is only one way to parse arrays -
D.array
which fails completely if any item fails.Desired Behavior
A combinator that would work like
D.array
but would always succeed, only discarding the failed items.Suggested Solution
I have written a prototype combinator
failsafeArray
for this, that works in a two-step manner: Firstly, thecollapse
combinator recovers a failed decoder and returnsE.Right(undefined)
. After this,undefined
items ale filtered out and the original type is recovered. I guess the use ofundefined
is not particularly elegant here and better solution could be achieved with internalDecoder
features which are not documented.Who does this impact? Who is this for?
Personally, I found that it is useful when parsing an array of items returned from an API, when I only care about the ones which match my decoder.
Discarding the items which do not parse also enables parsing certain attributes into unions of literals instead of strings while making the parser more future-proof and liberal.
Consider the following type with represents a hypothetical array of product attributes from an API:
I might however like to parse it into:
while discarding the items which do not match.
This might be useful for anybody parsing collections of items to which certain identifiers might be added in the future or the shape of only some items might change.
Now, let's suppose the objects in the array are of various shapes, but the shape actually depends on the
slug
property. If we put it this way, the items actually represent a tagged-union type which is open to extension by the API. In that manner, parsing only the array items of known slugs is dual to whatstruct
combinator does now in terms of objects (which is allowing for extension of objects by adding new keys while retaining compatibility).Describe alternatives you've considered
One alternative is to just use the
D.array
but this does not satisfy my need fully.I've also considered writing the above
collapse
function as a plain decoder instead of combinator:and using it in
union
with a decoder for item that could fail thus recovering the failure with value asundefined
- like this:, and then filtering out
undefined
as in the first code snippet. However this approach relies on the order of processingunion
which I guess should be commutative.Your environment