Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Schema: add fromJson combinator #621

Merged
merged 1 commit into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/young-clouds-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/schema": patch
---

Schema: add `fromJson` combinator
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2006,6 +2006,15 @@ const schema = S.ParseJson.pipe(S.compose(S.struct({ a: S.number })));

In this example, we've composed the `ParseJson` schema with a struct schema to ensure that the result will have a specific shape, including an object with a numeric property "a".

Alternatively, you can achieve the same result by using the equivalent built-in combinator `fromJson`:

```ts
import * as S from "@effect/schema/Schema";

// $ExpectType Schema<string, { readonly a: number; }>
const schema = S.fromJson(S.struct({ a: S.number }));
```

### Number transformations

#### NumberFromString
Expand Down
38 changes: 30 additions & 8 deletions docs/modules/Schema.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ Added in v1.0.0
- [string transformations](#string-transformations)
- [Lowercase](#lowercase)
- [Uppercase](#uppercase)
- [fromJson](#fromjson)
- [lowercase](#lowercase-1)
- [parseJson](#parsejson-1)
- [split](#split)
Expand Down Expand Up @@ -332,6 +333,7 @@ Added in v1.0.0
- [FromOptionalKeys (type alias)](#fromoptionalkeys-type-alias)
- [FromStruct (type alias)](#fromstruct-type-alias)
- [Join (type alias)](#join-type-alias)
- [JsonOptions (type alias)](#jsonoptions-type-alias)
- [Mutable (type alias)](#mutable-type-alias)
- [OptionalPropertySignature (interface)](#optionalpropertysignature-interface)
- [PropertySignature (interface)](#propertysignature-interface)
Expand Down Expand Up @@ -3096,6 +3098,19 @@ export declare const Uppercase: Schema<string, string>

Added in v1.0.0

## fromJson

The `fromJson` combinator offers a method to convert JSON strings into the `A` type using the underlying
functionality of `JSON.parse`. It also employs `JSON.stringify` for encoding.

**Signature**

```ts
export declare const fromJson: <I, A>(schema: Schema<I, A>, options?: JsonOptions) => Schema<string, A>
```

Added in v1.0.0

## lowercase

This combinator converts a string to lowercase
Expand All @@ -3116,14 +3131,7 @@ functionality of `JSON.parse`. It also employs `JSON.stringify` for encoding.
**Signature**

```ts
export declare const parseJson: <I, A extends string>(
self: Schema<I, A>,
options?: {
reviver?: Parameters<typeof JSON.parse>[1]
replacer?: Parameters<typeof JSON.stringify>[1]
space?: Parameters<typeof JSON.stringify>[2]
}
) => Schema<I, unknown>
export declare const parseJson: <I, A extends string>(self: Schema<I, A>, options?: JsonOptions) => Schema<I, unknown>
```

Added in v1.0.0
Expand Down Expand Up @@ -3885,6 +3893,20 @@ export type Join<T> = T extends [infer Head, ...infer Tail]

Added in v1.0.0

## JsonOptions (type alias)

**Signature**

```ts
export type JsonOptions = {
readonly reviver?: Parameters<typeof JSON.parse>[1]
readonly replacer?: Parameters<typeof JSON.stringify>[1]
readonly space?: Parameters<typeof JSON.stringify>[2]
}
```

Added in v1.0.0

## Mutable (type alias)

Make all properties in T mutable
Expand Down
30 changes: 25 additions & 5 deletions src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2007,18 +2007,26 @@ export const split: {
)
)

/**
* @since 1.0.0
*/
export type JsonOptions = {
readonly reviver?: Parameters<typeof JSON.parse>[1]
readonly replacer?: Parameters<typeof JSON.stringify>[1]
readonly space?: Parameters<typeof JSON.stringify>[2]
}

/**
* The `parseJson` combinator offers a method to convert JSON strings into the `unknown` type using the underlying
* functionality of `JSON.parse`. It also employs `JSON.stringify` for encoding.
*
* @category string transformations
* @since 1.0.0
*/
export const parseJson = <I, A extends string>(self: Schema<I, A>, options?: {
reviver?: Parameters<typeof JSON.parse>[1]
replacer?: Parameters<typeof JSON.stringify>[1]
space?: Parameters<typeof JSON.stringify>[2]
}): Schema<I, unknown> => {
export const parseJson = <I, A extends string>(
self: Schema<I, A>,
options?: JsonOptions
): Schema<I, unknown> => {
return transformOrFail(
self,
unknown,
Expand All @@ -2036,6 +2044,18 @@ export const parseJson = <I, A extends string>(self: Schema<I, A>, options?: {
)
}

/**
* The `fromJson` combinator offers a method to convert JSON strings into the `A` type using the underlying
* functionality of `JSON.parse`. It also employs `JSON.stringify` for encoding.
*
* @category string transformations
* @since 1.0.0
*/
export const fromJson = <I, A>(
schema: Schema<I, A>,
options?: JsonOptions
): Schema<string, A> => compose(parseJson(string, options), schema)

// ---------------------------------------------
// string constructors
// ---------------------------------------------
Expand Down
50 changes: 50 additions & 0 deletions test/Schema/fromJson.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as S from "@effect/schema/Schema"
import * as Util from "@effect/schema/test/util"
import { describe, it } from "vitest"

describe("Schema/fromJson", () => {
it("decoding", async () => {
const schema = S.fromJson(S.struct({ a: S.number }))
await Util.expectParseSuccess(schema, `{"a":1}`, { a: 1 })
await Util.expectParseFailure(
schema,
`{"a"}`,
Util.isBun
? `JSON Parse error: Expected ':' before value in object property definition`
: `Expected ':' after property name in JSON at position 4`
)
await Util.expectParseFailure(schema, `{"a":"b"}`, `/a Expected number, actual "b"`)
})

it("reviver", async () => {
const schema = S.fromJson(S.struct({ a: S.number, b: S.string }), {
reviver: (key, value) => key === "a" ? value + 1 : value
})
await Util.expectParseSuccess(schema, `{"a":1,"b":"b"}`, { a: 2, b: "b" })
})

it("encoding", async () => {
const schema = S.ParseJson.pipe(S.compose(S.struct({ a: S.number })))
await Util.expectEncodeSuccess(schema, { a: 1 }, `{"a":1}`)
})

it("replacer", async () => {
const schema = S.fromJson(S.struct({ a: S.number, b: S.string }), { replacer: ["b"] })
await Util.expectEncodeSuccess(
schema,
{ a: 1, b: "b" },
`{"b":"b"}`
)
})

it("space", async () => {
const schema = S.fromJson(S.struct({ a: S.number }), { space: 2 })
await Util.expectEncodeSuccess(
schema,
{ a: 1 },
`{
"a": 1
}`
)
})
})
2 changes: 1 addition & 1 deletion test/Schema/parseJson.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as S from "@effect/schema/Schema"
import * as Util from "@effect/schema/test/util"
import { describe, it } from "vitest"

describe("Schema/ParseJson", () => {
describe("Schema/parseJson", () => {
it("decoding", async () => {
const schema = S.ParseJson
await Util.expectParseSuccess(schema, "{}", {})
Expand Down
Loading