tiny-decoders
Advanced tools
Changelog
Version 23.0.0 (2023-10-30)
This release renames fieldsAuto
to fields
. Both of those functions used to exist, but fields
was deprecated in version 11.0.0 and removed in version 14.0.0. There’s no need for the Auto
suffix anymore.
Changelog
Version 22.0.0 (2023-10-30)
This release renames fieldsUnion
to taggedUnion
since it better describes what it is, and it goes along better with the tag
function.
Changelog
Version 21.0.0 (2023-10-30)
This release renames nullable
to nullOr
to be consistent with undefinedOr
.
Changelog
Version 20.1.0 (2023-10-30)
This release adds a JSON
object with parse
and stringify
methods, similar to the standard global JSON
object. The difference is that tiny-decoder’s versions also take a Codec
, which makes them safer. Read more about it in the documentation.
Changelog
Version 20.0.0 (2023-10-29)
This release adds more support for primitives.
These are the primitive types:
type primitive = bigint | boolean | number | string | symbol | null | undefined;
stringUnion
has been renamed to primitiveUnion
and now works with literals of any primitive type, not just strings. You can now create a codec for a union of numbers, for example.tag
now accepts literals of any primitive type, not just strings. For example, this allows for easily decoding a tagged union where the discriminator is isAdmin: true
and isAdmin: false
, or a tagged union where the tags are numbers.bigint
codec has been added – a codec for bigint
values. There are now codecs for all primitive types, except:
symbol
: I don’t think this is useful. Use const mySymbol: unique symbol = Symbol(); primitiveUnion([mySymbol])
instead.undefined
: Use primitiveUnion([undefined])
if needed.null
: Use primitiveUnion([null])
if needed.multi
now supports bigint
and symbol
, covering all primitive types. Additionally, since multi
is basically the JavaScript typeof
operator as a codec, it now also supports function
.repr
now recognizes bigint
and prints for example 123n
instead of BigInt
. It already supported symbols (and all other primitive types) since before.DecoderError
type had slight changes due to the above. If all you do with errors is format(error)
, then you won’t notice.In short, all you need to do to upgrade is change stringUnion
into primitiveUnion
.
Changelog
Version 19.0.0 (2023-10-29)
This release introduces Codec
:
type Codec<Decoded, Encoded = unknown> = {
decoder: (value: unknown) => DecoderResult<Decoded>;
encoder: (value: Decoded) => Encoded;
};
A codec is an object with a decoder and an encoder.
The decoder of a codec is the Decoder
type from previous versions of tiny-decoders.
An encoder is a function that turns Decoded
back into what the input looked like. You can think of it as “turning Decoded
back into unknown
”, but usually the Encoded
type variable is inferred to something more precise.
All functions in tiny-decoders have been changed to work with Codec
s instead of Decoder
s (and the Decoder
type does not exist anymore – it is only part of the new Codec
type).
Overall, most things are the same. Things accept and return Codec
s instead of Decoder
s now, but many times it does not affect your code.
The biggest changes are:
Decoder
, a Codec
is not callable. You need to add .decoder
. For example, change myDecoder(data)
to myDecoder.decoder(data)
. Then rename to myCodec.decoder(data)
for clarity.map
and flatMap
now take two functions: The same function as before for transforming the decoded data, but now also a second function for turning the data back again. This is usually trivial to implement.Decoder
was just a function. A custom Codec
is an object with decoder
and encoder
fields. Wrap your existing decoder function in such an object, and then implement the encoder (the inverse of the decoder). This is usually trivial as well.Finally, this release adds a couple of small things:
InferEncoded
utility type. Infer
still infers the type for the decoder. InferEncoded
infers the type for the encoder.unknown
codec. It’s occasionally useful, and now that you need to specify both a decoder and an encoder it crossed the triviality threshold for being included in the package.The motivations for codecs are:
fieldsAuto
and inconsistent encoded common fields in fieldsUnion
.map
, flatMap
, field
and tag
to turn JSON into nicer or more type safe types, you can now easily reverse that again when you need to serialize back to JSON.Changelog
Version 18.0.0 (2023-10-29)
This release removes the second type variable from Decoder
.
Before:
type Decoder<T, U = unknown> = (value: U) => DecoderResult<T>;
After:
type Decoder<T> = (value: unknown) => DecoderResult<T>;
This change unlocks further changes that will come in future releases.
Changelog
Version 17.0.1 (2023-10-29)
Fixed: fieldsAuto
now reports the correct field name when there’s an error in a renamed field.
const decoder = fieldsAuto({
firstName: field(string, { renameFrom: "first_name" }),
});
decoder({ first_name: false });
Before:
At root["firstName"]:
Expected a string
Got: false
After:
At root["first_name"]:
Expected a string
Got: false
Changelog
Version 17.0.0 (2023-10-28)
This release removes the second argument from undefinedOr
and nullable
, which was a default value to use in place of undefined
or null
, respectively. You now need to use map
instead. This change unlocks further changes that will come in future releases.
Before:
const decoder1 = undefinedOr(string, "default value");
const decoder2 = nullable(string, undefined);
After:
const decoder1 = map(undefinedOr(string), (value) => value ?? "default value");
const decoder2 = map(nullable(string), (value) => value ?? undefined);
Changelog
Version 16.0.0 (2023-10-28)
This release changes decoders from throwing errors to returning a DecoderResult
:
type Decoder<T> = (value: unknown) => DecoderResult<T>;
type DecoderResult<T> =
| {
tag: "DecoderError";
error: DecoderError;
}
| {
tag: "Valid";
value: T;
};
This change is nice because:
try-catch
when you run a decoder, which is annoying due to the caught error is typed as any
or unknown
, which required an error instanceof DecoderError
check..format()
method of DecoderErrors
, but now it’s more obvious how to deal with errors.Decoder
tells the whole story: Now it’s explicit that they can fail, while previously it was implicit.DecoderError
is now a plain object instead of a class, and DecoderErrorVariant
is no longer exposed – there’s just DecoderError
now. Use the new format
function to turn a DecoderError
into a string, similar to what DecoderError.prototype.format
did before.
You now have to use the Infer
utility type (added in version 15.1.0) instead of ReturnType
. ReturnType
gives you a DecoderResult<T>
while Infer
gives you just T
.
chain
has been removed and replaced with map
and flatMap
. In all places you used chain
, you need to switch to map
if the operation cannot fail (you just transform the data), or flatMap
if it can fail. For flatMap
, you should not throw errors but instead return a DecoderResult
. You might need to use a try-catch
to do this. For example, if you used the RegExp
constructor in chain
before to create a regex, you might have relied on tiny-decoders catching the errors for invalid regex syntax errors. Now you need to catch that yourself. Note that TypeScript won’t help you what you need to catch. Similarly, you also need to return a DecoderError
instead of throwing in custom decoders.
This function can potentially help you migrate tricky decoders where you’re not sure if something might throw errors. It wraps a given decoder in a try-catch
and returns a new decoder that swallows everything as DecoderError
s.
function catcher<T>(decoder: Decoder<T>): Decoder<T> {
return (value) => {
try {
return decoder(value);
} catch (error) {
return {
tag: "DecoderError",
error: {
tag: "custom",
message: error instanceof Error ? error.message : String(error),
got: value,
path: [],
},
};
}
};
}