|
1 | 1 | Note: I’m currently working on several breaking changes to tiny-decoders, but I’m trying out releasing them piece by piece. The idea is that you can either upgrade version by version only having to deal with one or a few breaking changes at a time, or wait and do a bunch of them at the same time.
|
2 | 2 |
|
| 3 | +### Version 11.0.0 (unreleased) |
| 4 | + |
| 5 | +This release deprecates `fields`, and makes `fieldsAuto` more powerful so that it can do most of what only `fields` could before. Removing `fields` unlocks further changes that will come in future releases. It’s also nice to have just one way of decoding objects (`fieldsAuto`), instead of having two. Finally, the changes to `fieldsAuto` gets rid of a flawed design choice which solves several reported bugs: [#22](https://github.com/lydell/tiny-decoders/issues/22) and [#24](https://github.com/lydell/tiny-decoders/issues/24). |
| 6 | + |
| 7 | +- Changed: `optional` has been removed and replaced by `undefinedOr` and a new function called `field`. The `optional` function did two things: It made a decoder also accept `undefined`, and marked fields as optional. Now there’s one function for each use case. |
| 8 | + |
| 9 | +- Added: The new `field` function returns a `Field` type, which is a decoder with some metadata. The metadata tells whether the field is optional, and whether the field has a different name in the JSON object. |
| 10 | + |
| 11 | +- Changed: `fieldsAuto` takes an object like before, where the values are `Decoder`s like before, but now the values can be `Field`s as well (returned from the `field` function). Passing a plain `Decoder` instead of a `Field` is just a convenience shortcut for passing a `Field` with the default metadata (the field is required, and has the same name both in TypeScript and in JSON). |
| 12 | + |
| 13 | +- Changed: `fieldsAuto` no longer computes which fields are optional by checking if the type of the field includes `| undefined`. Instead, it’s based purely on the `Field` metadata. |
| 14 | + |
| 15 | +- Changed: `const myDecoder = fieldsAuto<MyType>({ /* ... */ })` now needs to be written as `const myDecoder: Decoder<MyType> = fieldsAuto({ /* ... */ })`. It is no longer recommended to specify the generic of `fieldsAuto`, and doing so does not mean the same thing anymore. Either annotate the decoder as any other, or don’t and infer the type. |
| 16 | + |
| 17 | +- Added: `recursive`. It’s needed when making a decoder for a recursive data structure using `fieldsAuto`. (Previously, the recommendation was to use `fields` for recursive objects.) |
| 18 | + |
| 19 | +- Changed: TypeScript 5+ is now required, because the above uses [const type parameters](https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#const-type-parameters)) (added in 5.0), and leads to the [exactOptionalPropertyTypes](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes) (added in 4.4) option in `tsconfig.json` being recommended (see the documentation for the `field` function for why). |
| 20 | + |
| 21 | +The motivation for the changes are: |
| 22 | + |
| 23 | +- Supporting TypeScript’s [exactOptionalPropertyTypes](https://devblogs.microsoft.com/typescript/announcing-typescript-4-4/#exact-optional-property-types) option. That option decouples optional fields (`field?:`) and union with undefined (`| undefined`). Now tiny-decoders has done that too. |
| 24 | + |
| 25 | +- Supporting generic decoders. Marking the fields as optional was previously done by looking for fields with `| undefined` in their type. However, if the type of a field is generic, TypeScript can’t know if the type is going to have `| undefined` until the generic type is instantiated with a concrete type. As such it couldn’t know if the field should be optional or not yet either. This resulted in it being very difficult and ugly trying to write a type annotation for a generic function returning a decoder – in practice it was unusable without forcing TypeScript to the wanted type annotation. [#24](https://github.com/lydell/tiny-decoders/issues/24) |
| 26 | + |
| 27 | +- Stop setting all optional fields to `undefined` when they are missing (rather than leaving them out). [#22](https://github.com/lydell/tiny-decoders/issues/22) |
| 28 | + |
| 29 | +- Better error messages for missing required fields. |
| 30 | + |
| 31 | + Before: |
| 32 | + |
| 33 | + ``` |
| 34 | + At root["firstName"]: |
| 35 | + Expected a string |
| 36 | +
|
| 37 | + Got: undefined |
| 38 | + ``` |
| 39 | + |
| 40 | + After: |
| 41 | + |
| 42 | + ``` |
| 43 | + At root: |
| 44 | + Expected an object with a field called: "firstName" |
| 45 | + Got: { |
| 46 | + "id": 1, |
| 47 | + "first_name": "John" |
| 48 | + } |
| 49 | + ``` |
| 50 | + |
| 51 | + In other words, `fieldsAuto` now checks if fields exist, rather than trying to access them regardless. Previously, `fieldsAuto` ran `decoderAtKey(object[key])` even when `key` did not exist in `object`, which is equivalent to `decoderAtKey(undefined)`. Whether or not that succeeded was up to if `decoderAtKey` was using `optional` or not. This resulted in the worse (but technically correct) error message. The new version of `fieldsAuto` knows if the field is supposed to be optional or not thanks to the `Field` type and the `field` function mentioned above. |
| 52 | + |
| 53 | + > **Warning** |
| 54 | + > Temporary behavior: If a field is missing and _not_ marked as optional, `fieldsAuto` still _tries_ the decoder at the field (passing `undefined` to it). If the decoder succeeds (because it allows `undefined` or succeeds for any input), that value is used. If it fails, the regular “missing field” error is thrown. This means that `fieldsAuto({ name: undefinedOr(string) })` successfully produces `{ name: undefined }` if given `{}` as input. It is supposed to fail in that case (because a required field is missing), but temporarily it does not fail. This is to support how `fieldsUnion` is used currently. When `fieldsUnion` is updated to a new API in an upcoming version of tiny-decoders, this temporary behavior in `fieldsAuto` will be removed. |
| 55 | +
|
| 56 | +- Being able to rename fields with `fieldsAuto`. Now you don’t need to refactor from `fieldsAuto` to `fields` anymore if you need to rename a field. This is done by using the `field` function. |
| 57 | + |
| 58 | +- Getting rid of `fields` unlocks further changes that will come in future releases. (Note: `fields` is only deprecated in this release, not removed.) |
| 59 | + |
| 60 | +Here’s an example illustrating the difference between optional fields and accepting `undefined`: |
| 61 | + |
| 62 | +```ts |
| 63 | +fieldsAuto({ |
| 64 | + // Required field. |
| 65 | + a: string, |
| 66 | + |
| 67 | + // Optional field. |
| 68 | + b: field(string, { optional: true }), |
| 69 | + |
| 70 | + // Required field that can be set to `undefined`: |
| 71 | + c: undefinedOr(string), |
| 72 | + |
| 73 | + // Optional field that can be set to `undefined`: |
| 74 | + d: field(undefinedOr(string), { optional: true }), |
| 75 | +}); |
| 76 | +``` |
| 77 | + |
| 78 | +The inferred type of the above is: |
| 79 | + |
| 80 | +```ts |
| 81 | +type Inferred = { |
| 82 | + a: string; |
| 83 | + b?: string; |
| 84 | + c: string | undefined; |
| 85 | + d?: string | undefined; |
| 86 | +}; |
| 87 | +``` |
| 88 | + |
| 89 | +In all places where you use `optional(x)` currently, you need to figure out if you should use `undefinedOr(x)` or `field(x, { optional: true })` or `field(undefinedOr(x), { optional: true })`. |
| 90 | + |
| 91 | +The `field` function also lets you rename fields. This means that you can refactor: |
| 92 | + |
| 93 | +```ts |
| 94 | +fields((field) => ({ |
| 95 | + firstName: field("first_name", string), |
| 96 | +})); |
| 97 | +``` |
| 98 | + |
| 99 | +Into: |
| 100 | + |
| 101 | +```ts |
| 102 | +fieldsAuto({ |
| 103 | + firstName: field(string, { renameFrom: "first_name" }), |
| 104 | +}); |
| 105 | +``` |
| 106 | + |
| 107 | +If you used `fields` for other reasons, you can refactor them away by using `recursive`, `chain` and writing custom decoders. |
| 108 | + |
| 109 | +Read the documentation for `fieldsAuto` and `field` to learn more about how they work. |
| 110 | + |
3 | 111 | ### Version 10.0.0 (2023-10-15)
|
4 | 112 |
|
5 | 113 | Changed: `multi` has a new API.
|
|
0 commit comments