You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+57-12Lines changed: 57 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,28 +2,62 @@ Note: I’m currently working on several breaking changes to tiny-decoders, but
2
2
3
3
### Version 11.0.0 (unreleased)
4
4
5
-
- Changed: `fieldsAuto`works slightly differently. As part of that change, `optional` has been removed and replaced by `undefinedOr` and `field`. These changes make `fields` redundant, so it has been deprecated.
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
6
7
-
-Added: `recursive`. It’s needed now that `fields` has been deprecated.
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
8
9
-
- Changed: TypeScript 5+, 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 [exactOptionalPropertyTypes](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes) (added in 4.4) option in `tsconfig.json` being recommended.
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).
- Stop setting all optional fields to `undefined` when they are missing (rather than leaving them out).
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
+
16
29
- Better error messages for missing required fields.
17
-
- 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.
18
-
- Getting rid of `fields` unlocks further changes that will come in future releases. (Note: `fields` is only deprecated in this release, not removed.)
19
30
20
-
The `optional` function did two things: It made a decoder also accept `undefined`, and marked fields as optional. Marking the fields as optional was 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. This design also doesn’t let you use `exactOptionalPropertyTypes` as intended. The whole concept was flawed.
31
+
Before:
21
32
22
-
`optional` has therefore been renamed to `undefinedOr`, and now it does only one thing: Makes a decoder also accept `undefined`.
33
+
```
34
+
At root["firstName"]:
35
+
Expected a string
23
36
24
-
The `field` function has been introduced to mark fields as optional, separating the two jobs that `optional` used to do.
37
+
Got: undefined
38
+
```
25
39
26
-
For example:
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`:
27
61
28
62
```ts
29
63
fieldsAuto({
@@ -41,6 +75,17 @@ fieldsAuto({
41
75
});
42
76
```
43
77
78
+
The inferred type of the above is:
79
+
80
+
```ts
81
+
typeInferred= {
82
+
a:string;
83
+
b?:string;
84
+
c:string|undefined;
85
+
d?:string|undefined;
86
+
};
87
+
```
88
+
44
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 })`.
45
90
46
91
The `field` function also lets you rename fields. This means that you can refactor:
Copy file name to clipboardExpand all lines: README.md
+42-4Lines changed: 42 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,7 +17,7 @@ npm install tiny-decoders
17
17
18
18
tiny-decoders requires TypeScript 5+ (because it uses [const type parameters](https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#const-type-parameters)).
19
19
20
-
It is recommended to enable the [exactOptionalPropertyTypes](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes) option in `tsconfig.json`.
20
+
It is recommended to enable the [exactOptionalPropertyTypes](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes) option in `tsconfig.json` – see the note at the [field](#field) function.
21
21
22
22
Note that it is possible to use tiny-decoders in plain JavaScript without type checking as well.
23
23
@@ -79,7 +79,7 @@ You can even [infer the type from the decoder](#type-inference) instead of writi
79
79
typeUser2=ReturnType<typeofuserDecoder2>;
80
80
```
81
81
82
-
The above produces the `User` type already shown above.
82
+
`User2` above is equivalent to the `User` type already shown earlier.
83
83
84
84
## Decoder<T>
85
85
@@ -494,6 +494,10 @@ function field<Decoded, const Meta extends FieldMeta>(
494
494
meta:Meta,
495
495
):Field<Decoded, Meta>;
496
496
497
+
typeField<Decoded, MetaextendsFieldMeta> =Meta& {
498
+
decoder:Decoder<Decoded>;
499
+
};
500
+
497
501
typeFieldMeta= {
498
502
renameFrom?:string|undefined;
499
503
optional?:boolean|undefined;
@@ -508,6 +512,35 @@ This function takes a decoder and lets you:
508
512
509
513
Use it with [fieldsAuto](#fieldsAuto).
510
514
515
+
Here’s an example illustrating the difference between `field(string, { optional: true })` and `undefinedOr(string)`:
> It is recommended to enable the [exactOptionalPropertyTypes](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes) option in `tsconfig.json`.
513
546
>
@@ -543,7 +576,7 @@ Use it with [fieldsAuto](#fieldsAuto).
543
576
>
544
577
> That gives the same inferred type, but also supports decoding the `name` field being set to `undefined` explicitly.
545
578
>
546
-
> All in all, you avoid a slight gotcha with optional fields and inferred types if you use`exactOptionalPropertyTypes`.
579
+
> All in all, you avoid a slight gotcha with optional fields and inferred types if you enable`exactOptionalPropertyTypes`.
547
580
548
581
### fieldsUnion
549
582
@@ -945,10 +978,15 @@ Rather than first defining the type and then defining the decoder (which often f
945
978
```ts
946
979
const personDecoder =fieldsAuto({
947
980
name: string,
948
-
age: optional(number),
981
+
age: number,
949
982
});
950
983
951
984
typePerson=ReturnType<typeofpersonDecoder>;
985
+
// equivalent to:
986
+
typePerson= {
987
+
name:string;
988
+
age:number;
989
+
};
952
990
```
953
991
954
992
See the [type inference example](examples/type-inference.test.ts) for more details.
0 commit comments