From 8625f5c8afb01ddaa6b7cd9f7349ffc204d1c8ca Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 14:10:57 -0500 Subject: [PATCH 01/21] feat: Add JSON parsing examples --- website/docs/how-to-guides/_category_.json | 7 +- .../how-to-guides/parsing.json-strings.md | 69 +++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 website/docs/how-to-guides/parsing.json-strings.md diff --git a/website/docs/how-to-guides/_category_.json b/website/docs/how-to-guides/_category_.json index 69dca425..2b8c3629 100644 --- a/website/docs/how-to-guides/_category_.json +++ b/website/docs/how-to-guides/_category_.json @@ -1,7 +1,8 @@ { - "label": "How-To Guide", + "label": "How - To Guides", "position": 3, "link": { - "type": "generated-index" + "type": "generated-index", + "description": "5 minutes to practice the most important api-ts concepts." } -} +} \ No newline at end of file diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md new file mode 100644 index 00000000..1f697ab7 --- /dev/null +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -0,0 +1,69 @@ +# How To Parse JSON Strings + +## Basic JSON String Parsing +```typescript +import * as t from 'io-ts' + +// Define the expected structure +const UserCodec = t.type({ + name: t.string, + age: t.number, + email: t.string +}) + +// Parse JSON string and validate structure +const jsonString = '{"name": "Alice", "age": 30, "email": "alice@example.com"}' + +// First parse the JSON string +const parsed = JSON.parse(jsonString) + +// Then validate the parsed data +const result = UserCodec.decode(parsed) +// Success: { name: "Alice", age: 30, email: "alice@example.com" } + +// Example with invalid data +const invalidJson = '{"name": "Bob", "age": "30"}' // age should be number +const invalidParsed = JSON.parse(invalidJson) +const invalidResult = UserCodec.decode(invalidParsed) +// Error: Invalid value "30" supplied to : { name: string, age: number, email: string }/age: number +``` + +## Nested JSON String Parsing + +```typescript +import * as t from 'io-ts' +import { DateFromISOString } from 'io-ts-types' + +// Define a codec that handles nested structures and dates +const EventCodec = t.type({ + id: t.string, + timestamp: DateFromISOString, + data: t.type({ + title: t.string, + participants: t.array(t.type({ + id: t.string, + role: t.union([ + t.literal('organizer'), + t.literal('attendee') + ]) + })) + }) +}) + +// Example JSON string with nested structure +const jsonString = `{ + "id": "evt_123", + "timestamp": "2024-01-15T10:30:00Z", + "data": { + "title": "Team Meeting", + "participants": [ + {"id": "user_1", "role": "organizer"}, + {"id": "user_2", "role": "attendee"} + ] + } +}` + +// Parse and validate in one step +const result = EventCodec.decode(JSON.parse(jsonString)) +// Success: Parsed with proper Date object and validated structure +``` \ No newline at end of file From b85867b2d60d3ee1e9dfd6c1d40a35aa6f2a8878 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 15:34:29 -0500 Subject: [PATCH 02/21] chore: use JSONFromString() --- .../how-to-guides/parsing.json-strings.md | 68 ++++--------------- 1 file changed, 13 insertions(+), 55 deletions(-) diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index 1f697ab7..dc668434 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -1,8 +1,9 @@ -# How To Parse JSON Strings +# How To Parse JSON Strings Declaratively -## Basic JSON String Parsing +## Declarative JSON Parsing and Validation ```typescript import * as t from 'io-ts' +import { JSONFromString } from 'io-ts-types' // Define the expected structure const UserCodec = t.type({ @@ -11,59 +12,16 @@ const UserCodec = t.type({ email: t.string }) -// Parse JSON string and validate structure -const jsonString = '{"name": "Alice", "age": 30, "email": "alice@example.com"}' +const Data = '{"name": "Alice", "age": 30, "email": "alice@example.com"}' -// First parse the JSON string -const parsed = JSON.parse(jsonString) +// Combine parsing and validation declaratively +const decoded = JSONFromString(UserCodec).decode(Data) -// Then validate the parsed data -const result = UserCodec.decode(parsed) -// Success: { name: "Alice", age: 30, email: "alice@example.com" } - -// Example with invalid data -const invalidJson = '{"name": "Bob", "age": "30"}' // age should be number -const invalidParsed = JSON.parse(invalidJson) -const invalidResult = UserCodec.decode(invalidParsed) -// Error: Invalid value "30" supplied to : { name: string, age: number, email: string }/age: number -``` - -## Nested JSON String Parsing - -```typescript -import * as t from 'io-ts' -import { DateFromISOString } from 'io-ts-types' - -// Define a codec that handles nested structures and dates -const EventCodec = t.type({ - id: t.string, - timestamp: DateFromISOString, - data: t.type({ - title: t.string, - participants: t.array(t.type({ - id: t.string, - role: t.union([ - t.literal('organizer'), - t.literal('attendee') - ]) - })) - }) -}) - -// Example JSON string with nested structure -const jsonString = `{ - "id": "evt_123", - "timestamp": "2024-01-15T10:30:00Z", - "data": { - "title": "Team Meeting", - "participants": [ - {"id": "user_1", "role": "organizer"}, - {"id": "user_2", "role": "attendee"} - ] - } -}` - -// Parse and validate in one step -const result = EventCodec.decode(JSON.parse(jsonString)) -// Success: Parsed with proper Date object and validated structure +if (decoded._tag === 'Right') { + // Success: Valid data + console.log(decoded.right) // Parsed and validated data +} else { + // Error: Invalid data + console.error(decoded.left) // Validation error details +} ``` \ No newline at end of file From eb63dde069b95b0f4417cf1621780649e35f9c95 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 15:38:31 -0500 Subject: [PATCH 03/21] chore: add new line --- website/docs/how-to-guides/parsing.json-strings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index dc668434..28696445 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -24,4 +24,4 @@ if (decoded._tag === 'Right') { // Error: Invalid data console.error(decoded.left) // Validation error details } -``` \ No newline at end of file +``` From 88d072bba1faa311e426bb1688bfbccdbfe9bb80 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 15:42:05 -0500 Subject: [PATCH 04/21] chore: nix flake --- website/docs/how-to-guides/parsing.json-strings.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index 28696445..dc123902 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -9,19 +9,19 @@ import { JSONFromString } from 'io-ts-types' const UserCodec = t.type({ name: t.string, age: t.number, - email: t.string -}) + email: t.string, +}); -const Data = '{"name": "Alice", "age": 30, "email": "alice@example.com"}' +const Data = '{"name": "Alice", "age": 30, "email": "alice@example.com"}'; // Combine parsing and validation declaratively -const decoded = JSONFromString(UserCodec).decode(Data) +const decoded = JSONFromString(UserCodec).decode(Data); if (decoded._tag === 'Right') { // Success: Valid data - console.log(decoded.right) // Parsed and validated data + console.log(decoded.right); // Parsed and validated data } else { // Error: Invalid data - console.error(decoded.left) // Validation error details + console.error(decoded.left); // Validation error details } ``` From 49c0f493d0a107ea7c4259b16c42e090dbbec044 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 15:45:59 -0500 Subject: [PATCH 05/21] chore: skill issues --- website/docs/how-to-guides/_category_.json | 2 +- website/docs/how-to-guides/parsing.json-strings.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/how-to-guides/_category_.json b/website/docs/how-to-guides/_category_.json index 2b8c3629..efc0295f 100644 --- a/website/docs/how-to-guides/_category_.json +++ b/website/docs/how-to-guides/_category_.json @@ -5,4 +5,4 @@ "type": "generated-index", "description": "5 minutes to practice the most important api-ts concepts." } -} \ No newline at end of file +} diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index dc123902..c2a6bd65 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -2,8 +2,8 @@ ## Declarative JSON Parsing and Validation ```typescript -import * as t from 'io-ts' -import { JSONFromString } from 'io-ts-types' +import * as t from 'io-ts'; +import { JSONFromString } from 'io-ts-types'; // Define the expected structure const UserCodec = t.type({ From 4f5a3b4b5c6434c2cc67e53ca2791f808c50c866 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 15:48:23 -0500 Subject: [PATCH 06/21] chore: real skill issues --- website/docs/how-to-guides/parsing.json-strings.md | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index c2a6bd65..c2cc5a88 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -1,6 +1,7 @@ # How To Parse JSON Strings Declaratively ## Declarative JSON Parsing and Validation + ```typescript import * as t from 'io-ts'; import { JSONFromString } from 'io-ts-types'; From 941e1f19e11e7c933c1b84d336133426eea6e636 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Wed, 12 Feb 2025 07:47:13 -0500 Subject: [PATCH 07/21] chore: sus code --- packages/io-ts-http/src/httpRequest.ts | 4 ++-- website/docs/how-to-guides/parsing.json-strings.md | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/io-ts-http/src/httpRequest.ts b/packages/io-ts-http/src/httpRequest.ts index b48bc2ec..ab5570ca 100644 --- a/packages/io-ts-http/src/httpRequest.ts +++ b/packages/io-ts-http/src/httpRequest.ts @@ -20,7 +20,7 @@ export type HttpRequestCombinatorProps = { params?: NonNullable; query?: NonNullable; headers?: NonNullable; - body?: NonNullable; + body?: t.Mixed; }; /** @@ -35,7 +35,7 @@ type EmitOutputTypeErrors< > = P extends undefined ? P : { - [K in keyof P & string]: P[K] extends t.Type + [K in keyof P & string]: P[K] extends t.Type ? P[K] : `Codec's output type is not assignable to \`${OName}\`. Try using one like \`NumberFromString\``; }; diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index c2cc5a88..327e8c96 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -4,7 +4,7 @@ ```typescript import * as t from 'io-ts'; -import { JSONFromString } from 'io-ts-types'; +import { JsonFromString } from 'io-ts-types'; // Define the expected structure const UserCodec = t.type({ @@ -16,13 +16,15 @@ const UserCodec = t.type({ const Data = '{"name": "Alice", "age": 30, "email": "alice@example.com"}'; // Combine parsing and validation declaratively -const decoded = JSONFromString(UserCodec).decode(Data); +const decoded = JsonFromString.decode(Data); +// Check if the decoding was successful if (decoded._tag === 'Right') { - // Success: Valid data - console.log(decoded.right); // Parsed and validated data + // Validate the decoded data using the UserCodec + const validated = UserCodec.decode(decoded.right); + console.log(validated); // Right { name: 'Alice', age: 30, email: ' } else { - // Error: Invalid data - console.error(decoded.left); // Validation error details + console.error('Decoding failed:', decoded.left); } + ``` From acb177c1d1bf6fbd4186663b939d833edec785d9 Mon Sep 17 00:00:00 2001 From: Young Jun Joo <157854752+youngjungithub@users.noreply.github.com> Date: Wed, 12 Feb 2025 07:54:52 -0500 Subject: [PATCH 08/21] Update httpRequest.ts --- packages/io-ts-http/src/httpRequest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/io-ts-http/src/httpRequest.ts b/packages/io-ts-http/src/httpRequest.ts index ab5570ca..b48bc2ec 100644 --- a/packages/io-ts-http/src/httpRequest.ts +++ b/packages/io-ts-http/src/httpRequest.ts @@ -20,7 +20,7 @@ export type HttpRequestCombinatorProps = { params?: NonNullable; query?: NonNullable; headers?: NonNullable; - body?: t.Mixed; + body?: NonNullable; }; /** @@ -35,7 +35,7 @@ type EmitOutputTypeErrors< > = P extends undefined ? P : { - [K in keyof P & string]: P[K] extends t.Type + [K in keyof P & string]: P[K] extends t.Type ? P[K] : `Codec's output type is not assignable to \`${OName}\`. Try using one like \`NumberFromString\``; }; From cf06230b54e83109e962aafaed0b4a036bdc5ea5 Mon Sep 17 00:00:00 2001 From: Young Jun Joo <157854752+youngjungithub@users.noreply.github.com> Date: Wed, 12 Feb 2025 07:58:57 -0500 Subject: [PATCH 09/21] nix flake --- website/docs/how-to-guides/parsing.json-strings.md | 1 - 1 file changed, 1 deletion(-) diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index 327e8c96..871c8bb9 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -26,5 +26,4 @@ if (decoded._tag === 'Right') { } else { console.error('Decoding failed:', decoded.left); } - ``` From b1b30674ffcccf34d748b724753aa1c873684ae8 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Thu, 20 Feb 2025 14:52:24 -0500 Subject: [PATCH 10/21] feat: parse, don't vaidate --- website/docs/intro.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/website/docs/intro.md b/website/docs/intro.md index 8d78bd44..48261a5b 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -2,11 +2,6 @@ sidebar_position: 1 --- -### Lorem ipsum +# Introduction -```python hello.py -# mark[16:24] -print("This is Code Hike") -``` - -Lorem ipsum dolor sit amet. +`io-ts-http` brings type safety to HTTP data handling in TypeScript by embracing the "[parse, don't validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/)" philosophy. Rather than simply checking if incoming HTTP data is valid, it also transforms raw, less-structured data (like strings or JSON) into strongly typed, precise objects using the io-ts library. This parsing happens at the system boundary, ensuring all types have use cases in your code. Once parsed, you can trust that the data satisfies both [type and semantic analysis](https://bitgo.github.io/api-ts/docs/tutorial-basics/create-an-api-spec/#what-problem-does-io-ts-http-solve). From 0cf9942051ce6f9e0db4e70f0a19abceec19fe89 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Thu, 20 Feb 2025 14:59:54 -0500 Subject: [PATCH 11/21] chore: nix --- website/docs/intro.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/docs/intro.md b/website/docs/intro.md index 48261a5b..b334da53 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -4,4 +4,5 @@ sidebar_position: 1 # Introduction -`io-ts-http` brings type safety to HTTP data handling in TypeScript by embracing the "[parse, don't validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/)" philosophy. Rather than simply checking if incoming HTTP data is valid, it also transforms raw, less-structured data (like strings or JSON) into strongly typed, precise objects using the io-ts library. This parsing happens at the system boundary, ensuring all types have use cases in your code. Once parsed, you can trust that the data satisfies both [type and semantic analysis](https://bitgo.github.io/api-ts/docs/tutorial-basics/create-an-api-spec/#what-problem-does-io-ts-http-solve). +`io-ts-http` brings type safety to HTTP data handling in TypeScript by embracing the "[parse, don't validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/)" philosophy. Rather than simply checking if incoming HTTP data is valid, it also transforms raw, less-structured data (like strings or JSON) into strongly typed, precise objects using the io-ts library. This parsing happens at the system boundary, ensuring all types have use cases in your code. Once parsed, you can trust that the data satisfies both +[type and semantic analysis](https://bitgo.github.io/api-ts/docs/tutorial-basics/create-an-api-spec/#what-problem-does-io-ts-http-solve). From e64f7dbc1aa6a04923a241501f1bb8b475033fb9 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Thu, 20 Feb 2025 15:28:28 -0500 Subject: [PATCH 12/21] chore: nix --- website/docs/intro.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/website/docs/intro.md b/website/docs/intro.md index b334da53..7bf20bb2 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -4,5 +4,10 @@ sidebar_position: 1 # Introduction -`io-ts-http` brings type safety to HTTP data handling in TypeScript by embracing the "[parse, don't validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/)" philosophy. Rather than simply checking if incoming HTTP data is valid, it also transforms raw, less-structured data (like strings or JSON) into strongly typed, precise objects using the io-ts library. This parsing happens at the system boundary, ensuring all types have use cases in your code. Once parsed, you can trust that the data satisfies both -[type and semantic analysis](https://bitgo.github.io/api-ts/docs/tutorial-basics/create-an-api-spec/#what-problem-does-io-ts-http-solve). +`io-ts-http` brings type safety to HTTP data handling in TypeScript by embracing the +"[parse, don't validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/)" +philosophy. Rather than simply checking if incoming HTTP data is valid, it also +transforms raw, less-structured data (like strings or JSON) into strongly typed, precise +objects using the io-ts library. This parsing happens at the system boundary, ensuring +all types have use cases in your code. Once parsed, you can trust that the data +satisfies both [type and semantic analysis](https://bitgo.github.io/api-ts/docs/tutorial-basics/create-an-api-spec/#what-problem-does-io-ts-http-solve). From 6214c8f2a89f021d0e7d9a2d41b305e02aaa967a Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Thu, 20 Feb 2025 15:30:33 -0500 Subject: [PATCH 13/21] chore: nix --- website/docs/intro.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/docs/intro.md b/website/docs/intro.md index 7bf20bb2..443b67ec 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -10,4 +10,5 @@ philosophy. Rather than simply checking if incoming HTTP data is valid, it also transforms raw, less-structured data (like strings or JSON) into strongly typed, precise objects using the io-ts library. This parsing happens at the system boundary, ensuring all types have use cases in your code. Once parsed, you can trust that the data -satisfies both [type and semantic analysis](https://bitgo.github.io/api-ts/docs/tutorial-basics/create-an-api-spec/#what-problem-does-io-ts-http-solve). +satisfies both +[type and semantic analysis](https://bitgo.github.io/api-ts/docs/tutorial-basics/create-an-api-spec/#what-problem-does-io-ts-http-solve). From 5653fd963be59fa21897f5815f4352ca7586dacb Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 25 Feb 2025 14:08:13 -0500 Subject: [PATCH 14/21] fix: apply doc feedback --- website/docs/intro.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/website/docs/intro.md b/website/docs/intro.md index 443b67ec..4fb660fa 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -2,13 +2,16 @@ sidebar_position: 1 --- -# Introduction +# Introduction -`io-ts-http` brings type safety to HTTP data handling in TypeScript by embracing the -"[parse, don't validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/)" -philosophy. Rather than simply checking if incoming HTTP data is valid, it also -transforms raw, less-structured data (like strings or JSON) into strongly typed, precise -objects using the io-ts library. This parsing happens at the system boundary, ensuring -all types have use cases in your code. Once parsed, you can trust that the data -satisfies both -[type and semantic analysis](https://bitgo.github.io/api-ts/docs/tutorial-basics/create-an-api-spec/#what-problem-does-io-ts-http-solve). +`io-ts-http` brings type safety to HTTP data handling in TypeScript by embracing the +"[parse, don't validate]" philosophy. Rather than simply checking if incoming HTTP data +is valid, it also parse raw, less-structured data (like strings or JSON) into strongly +typed, precise objects using the io-ts library. This parsing happens at the system +boundary, ensuring all types have use cases in your code. Once parsed, you can trust +that the data satisfies both [type and semantic analysis]. + +[parse, don't validate]: + https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ +[type and semantic analysis]: + https://bitgo.github.io/api-ts/docs/tutorial-basics/create-an-api-spec/#what-problem-does-io-ts-http-solve From f228a9ac1798ede3aa5c7938750c43699723485d Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 25 Feb 2025 14:12:10 -0500 Subject: [PATCH 15/21] fix: example code --- .../how-to-guides/parsing.json-strings.md | 82 +++++++++++++++---- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index 871c8bb9..73bcbf3e 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -1,28 +1,80 @@ -# How To Parse JSON Strings Declaratively +# Unpacking a Subtle Quirk in JsonFromString Codec -## Declarative JSON Parsing and Validation +There's a small detail in `JsonFromString` from the `io-ts-types` that is worth noting. +There are three type parameters to a codec `t.Type`. The third parameter +determines the type that the `decode` function receives. Most other codecs have the +third parameter set to `unknown`. But `JsonFromString`'s type is +`t.Type`. As a result, `JsonFromString` expects a string type +before passing it to `decode`. You can easily convert `JsonFromString` to +`t.Type` using `t.string`. See the example below: ```typescript import * as t from 'io-ts'; -import { JsonFromString } from 'io-ts-types'; +import { nonEmptyArray, JsonFromString, NumberFromString } from 'io-ts-types'; +import { httpRequest, optional } from '@api-ts/io-ts-http'; -// Define the expected structure -const UserCodec = t.type({ - name: t.string, - age: t.number, - email: t.string, +// Define the Filter type for the JSON string +const Filter = t.type({ + category: t.string, + tags: t.array(t.string), + price: t.type({ + min: t.number, + max: t.number, + }), }); -const Data = '{"name": "Alice", "age": 30, "email": "alice@example.com"}'; +// Define the SearchRequest codec +const SearchRequest = httpRequest({ + params: { + userId: NumberFromString, + }, + query: { + q: t.string, + filter: t.string.pipe(JsonFromString).pipe(Filter), + tags: nonEmptyArray(t.string), + sort: optional(t.string), + }, + headers: { + authorization: t.string, + }, +}); -// Combine parsing and validation declaratively -const decoded = JsonFromString.decode(Data); +// Example request object +const example = { + params: { + userId: '84938492', + }, + query: { + q: 'test', + filter: + '{"category":"books","tags":["crypto","trading"],"price":{"min":10,"max":50}}', + tags: ['tag1', 'tag2', 'tag3'], + sort: 'price', + }, + headers: { + authorization: 'Bearer token', + }, +}; -// Check if the decoding was successful +// Decode the example +const decoded = SearchRequest.decode(example); if (decoded._tag === 'Right') { - // Validate the decoded data using the UserCodec - const validated = UserCodec.decode(decoded.right); - console.log(validated); // Right { name: 'Alice', age: 30, email: ' + console.log(decoded); + /* + Expected decoded output + { + userId: 84938492, + q: 'test', + filter: { + category: 'books', + tags: ['crypto', 'trading'], + price: { min: 10, max: 50 }, + }, + tags: ['tag1', 'tag2', 'tag3'], + sort: 'price', + authorization: 'Bearer token', + }; + */ } else { console.error('Decoding failed:', decoded.left); } From be189c61cd33de02701ac5a72224c9b46368a0c1 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 25 Feb 2025 15:15:02 -0500 Subject: [PATCH 16/21] chore: add --- website/docs/intro.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/intro.md b/website/docs/intro.md index 4fb660fa..2a2389c4 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -7,7 +7,7 @@ sidebar_position: 1 `io-ts-http` brings type safety to HTTP data handling in TypeScript by embracing the "[parse, don't validate]" philosophy. Rather than simply checking if incoming HTTP data is valid, it also parse raw, less-structured data (like strings or JSON) into strongly -typed, precise objects using the io-ts library. This parsing happens at the system +typed, precise objects using the `io-ts` library. This parsing happens at the system boundary, ensuring all types have use cases in your code. Once parsed, you can trust that the data satisfies both [type and semantic analysis]. From 934a5624f3287534a835cba6acb08ee0dae6a67d Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 25 Feb 2025 15:23:53 -0500 Subject: [PATCH 17/21] fix: apply feedback --- .../docs/how-to-guides/parsing.json-strings.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index 73bcbf3e..e3ba2b21 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -1,12 +1,13 @@ # Unpacking a Subtle Quirk in JsonFromString Codec -There's a small detail in `JsonFromString` from the `io-ts-types` that is worth noting. -There are three type parameters to a codec `t.Type`. The third parameter -determines the type that the `decode` function receives. Most other codecs have the -third parameter set to `unknown`. But `JsonFromString`'s type is -`t.Type`. As a result, `JsonFromString` expects a string type -before passing it to `decode`. You can easily convert `JsonFromString` to -`t.Type` using `t.string`. See the example below: +There are three type parameters to a codec: `t.Type`. The third parameter +determines the type that the `decode` function receives. Most codecs have the third +parameter set to `unknown`. However, the type for `JsonFromString` in `io-ts-types` is +`t.Type`. Therefore, `JsonFromString` expects a string type before +passing it to `decode`. You can easily convert `JsonFromString` to +`t.Type` using `t.string`. + +For example: ```typescript import * as t from 'io-ts'; From 0529117aaa76f03963a7b4cfbe3f6541824ecdd3 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 25 Feb 2025 15:33:54 -0500 Subject: [PATCH 18/21] fix: apply feedback --- website/docs/intro.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/intro.md b/website/docs/intro.md index 2a2389c4..a0ae6213 100644 --- a/website/docs/intro.md +++ b/website/docs/intro.md @@ -6,10 +6,10 @@ sidebar_position: 1 `io-ts-http` brings type safety to HTTP data handling in TypeScript by embracing the "[parse, don't validate]" philosophy. Rather than simply checking if incoming HTTP data -is valid, it also parse raw, less-structured data (like strings or JSON) into strongly +is valid, it also parses raw, less-structured data (like strings or JSON) into strongly typed, precise objects using the `io-ts` library. This parsing happens at the system -boundary, ensuring all types have use cases in your code. Once parsed, you can trust -that the data satisfies both [type and semantic analysis]. +boundary, ensuring all types have use cases in your code. Once parsed, you can trust the +data satisfies both [type and semantic analysis]. [parse, don't validate]: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ From 3ac69577eb9e7ed9096257f9b6e4fd2d45766b1a Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 25 Feb 2025 16:00:28 -0500 Subject: [PATCH 19/21] fix: apply feedback --- website/docs/how-to-guides/_category_.json | 2 +- website/docs/how-to-guides/parsing.json-strings.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/how-to-guides/_category_.json b/website/docs/how-to-guides/_category_.json index efc0295f..4525a16f 100644 --- a/website/docs/how-to-guides/_category_.json +++ b/website/docs/how-to-guides/_category_.json @@ -1,5 +1,5 @@ { - "label": "How - To Guides", + "label": "How-To Guides", "position": 3, "link": { "type": "generated-index", diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index e3ba2b21..553f38f4 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -1,4 +1,4 @@ -# Unpacking a Subtle Quirk in JsonFromString Codec +# Understanding Atypical Type Behavior from JsonFromString in io-ts-types There are three type parameters to a codec: `t.Type`. The third parameter determines the type that the `decode` function receives. Most codecs have the third From 06ce95e0768e20a33662611a18be96e1cfab975f Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Thu, 27 Feb 2025 08:05:03 -0500 Subject: [PATCH 20/21] fix: title --- website/docs/how-to-guides/parsing.json-strings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index 553f38f4..80efb005 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -1,4 +1,4 @@ -# Understanding Atypical Type Behavior from JsonFromString in io-ts-types +# Decoding JSON from Headers, Query Parameters, and URL Parameters There are three type parameters to a codec: `t.Type`. The third parameter determines the type that the `decode` function receives. Most codecs have the third From e485b8a058604a70808bb770e565f3b93aeb7d08 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Thu, 27 Feb 2025 13:42:57 -0500 Subject: [PATCH 21/21] fix: apply feedback --- .../how-to-guides/parsing.json-strings.md | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/website/docs/how-to-guides/parsing.json-strings.md b/website/docs/how-to-guides/parsing.json-strings.md index 80efb005..9fdac5a2 100644 --- a/website/docs/how-to-guides/parsing.json-strings.md +++ b/website/docs/how-to-guides/parsing.json-strings.md @@ -1,11 +1,31 @@ # Decoding JSON from Headers, Query Parameters, and URL Parameters -There are three type parameters to a codec: `t.Type`. The third parameter -determines the type that the `decode` function receives. Most codecs have the third -parameter set to `unknown`. However, the type for `JsonFromString` in `io-ts-types` is -`t.Type`. Therefore, `JsonFromString` expects a string type before -passing it to `decode`. You can easily convert `JsonFromString` to -`t.Type` using `t.string`. +Though we know headers, url parameters, and query parameters will be received as a +`string` or `string[]` value, due to a limitation in api-ts, `httpRequest` only accepts +codecs that decode values starting from the `unknown` type. Consequently, decoding a +header, url parameter, or query parameter with a codec like `JsonFromString`, which can +only decode values typed as `string`, will produce a error like: + +``` +Type 'Type' is not assignable to type 'Mixed'. + Types of property 'validate' are incompatible. + Type 'Validate' is not assignable to type 'Validate'. + Type 'unknown' is not assignable to type 'string'. +``` + +There's a straightforward pattern you can use when you have a value typed as `unknown` +but need to decode it with a codec that can only decode a narrower type. This pattern is +called codec chaining: + +```typescript +declare const JsonFromString: t.Type; +declare const t.string: t.Type; + +const myCodec: t.Type = t.string.pipe(JsonFromString); +``` + +Here, `t.string` decodes a value from `unknown` to `string`, and then `JsonFromString` +decodes the same value from `string` to `Json`. For example: