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

Commit

Permalink
refactor parseJson
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti committed Dec 20, 2023
1 parent 8310159 commit 6d85ae1
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 142 deletions.
5 changes: 5 additions & 0 deletions .changeset/forty-rats-decide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/schema": minor
---

Schema: refactor `parseJson` to replace `ParseJson` and `fromJson`
36 changes: 19 additions & 17 deletions docs/modules/Schema.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,6 @@ Added in v1.0.0
- [Lowercase](#lowercase)
- [Trim](#trim)
- [Uppercase](#uppercase)
- [fromJson](#fromjson)
- [parseJson](#parsejson-1)
- [split](#split)
- [symbol](#symbol)
Expand Down Expand Up @@ -368,7 +367,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)
- [ParseJsonOptions (type alias)](#parsejsonoptions-type-alias)
- [PropertySignature (interface)](#propertysignature-interface)
- [Schema (namespace)](#schema-namespace)
- [Variance (interface)](#variance-interface)
Expand Down Expand Up @@ -3522,28 +3521,31 @@ export declare const Uppercase: Schema<string, string>

Added in v1.0.0

## fromJson
## parseJson

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.
The `parseJson` combinator provides a method to convert JSON strings into the `unknown` type using the underlying
functionality of `JSON.parse`. It also utilizes `JSON.stringify` for encoding.

You can optionally provide a `ParseJsonOptions` to configure both `JSON.parse` and `JSON.stringify` executions.

Optionally, you can pass a schema `Schema<I, A>` to obtain an `A` type instead of `unknown`.

**Signature**

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

Added in v1.0.0

## parseJson

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.

**Signature**
**Example**

```ts
export declare const parseJson: <I, A extends string>(self: Schema<I, A>, options?: JsonOptions) => Schema<I, unknown>
import * as S from "@effect/schema/Schema"
assert.deepStrictEqual(S.parseSync(S.parseJson())(`{"a":"1"}`), { a: "1" })
assert.deepStrictEqual(S.parseSync(S.parseJson(S.struct({ a: S.NumberFromString })))(`{"a":"1"}`), { a: 1 })
```

Added in v1.0.0
Expand Down Expand Up @@ -4333,12 +4335,12 @@ export type Join<T> = T extends [infer Head, ...infer Tail]

Added in v1.0.0

## JsonOptions (type alias)
## ParseJsonOptions (type alias)

**Signature**

```ts
export type JsonOptions = {
export type ParseJsonOptions = {
readonly reviver?: Parameters<typeof JSON.parse>[1]
readonly replacer?: Parameters<typeof JSON.stringify>[1]
readonly space?: Parameters<typeof JSON.stringify>[2]
Expand Down
47 changes: 24 additions & 23 deletions src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2113,25 +2113,39 @@ export const split = (separator: string): Schema<string, ReadonlyArray<string>>
/**
* @since 1.0.0
*/
export type JsonOptions = {
export type ParseJsonOptions = {
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.
* The `parseJson` combinator provides a method to convert JSON strings into the `unknown` type using the underlying
* functionality of `JSON.parse`. It also utilizes `JSON.stringify` for encoding.
*
* You can optionally provide a `ParseJsonOptions` to configure both `JSON.parse` and `JSON.stringify` executions.
*
* Optionally, you can pass a schema `Schema<I, A>` to obtain an `A` type instead of `unknown`.
*
* @example
* import * as S from "@effect/schema/Schema"
*
* assert.deepStrictEqual(S.parseSync(S.parseJson())(`{"a":"1"}`), { a: "1" })
* assert.deepStrictEqual(S.parseSync(S.parseJson(S.struct({ a: S.NumberFromString })))(`{"a":"1"}`), { a: 1 })
*
* @category string transformations
* @since 1.0.0
*/
export const parseJson = <I, A extends string>(
self: Schema<I, A>,
options?: JsonOptions
): Schema<I, unknown> => {
export const parseJson: {
<I, A>(schema: Schema<I, A>, options?: ParseJsonOptions): Schema<string, A>
(options?: ParseJsonOptions): Schema<string, unknown>
} = <I, A>(schema?: Schema<I, A> | ParseJsonOptions, o?: ParseJsonOptions) => {
if (isSchema(schema)) {
return compose(parseJson(o), schema)
}
const options: ParseJsonOptions | undefined = schema as any
return transformOrFail(
self,
string,
unknown,
(s, _, ast) =>
ParseResult.try({
Expand All @@ -2142,8 +2156,7 @@ export const parseJson = <I, A extends string>(
ParseResult.try({
try: () => JSON.stringify(u, options?.replacer, options?.space),
catch: (e: any) => ParseResult.parseError([ParseResult.type(ast, u, e.message)])
}),
{ strict: false }
})
)
}

Expand All @@ -2154,19 +2167,7 @@ export const parseJson = <I, A extends string>(
* @category string constructors
* @since 1.0.0
*/
export const ParseJson: Schema<string, unknown> = parseJson(string)

/**
* 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)
export const ParseJson: Schema<string, unknown> = parseJson()

// ---------------------------------------------
// string constructors
Expand Down
50 changes: 0 additions & 50 deletions test/Schema/fromJson.test.ts

This file was deleted.

141 changes: 89 additions & 52 deletions test/Schema/parseJson.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,99 @@ import * as S from "@effect/schema/Schema"
import * as Util from "@effect/schema/test/util"
import { describe, it } from "vitest"

describe("Schema/parseJson", () => {
it("decoding", async () => {
const schema = S.ParseJson
await Util.expectParseSuccess(schema, "{}", {})
await Util.expectParseSuccess(schema, `{"a":"b"}`, { "a": "b" })

await Util.expectParseFailure(
schema,
"",
Util.isBun ? `JSON Parse error: Unexpected EOF` : `Unexpected end of JSON input`
)
await Util.expectParseFailure(
schema,
"a",
Util.isBun
? `JSON Parse error: Unexpected identifier "a"`
: `Unexpected token 'a', "a" is not valid JSON`
)
await Util.expectParseFailure(
schema,
"{",
Util.isBun
? `JSON Parse error: Expected '}'`
: `Expected property name or '}' in JSON at position 1`
)
})
describe("Schema > parseJson", () => {
describe("parseJson()", () => {
it("decoding", async () => {
const schema = S.parseJson()
await Util.expectParseSuccess(schema, "{}", {})
await Util.expectParseSuccess(schema, `{"a":"b"}`, { "a": "b" })

await Util.expectParseFailure(
schema,
"",
Util.isBun ? `JSON Parse error: Unexpected EOF` : `Unexpected end of JSON input`
)
await Util.expectParseFailure(
schema,
"a",
Util.isBun
? `JSON Parse error: Unexpected identifier "a"`
: `Unexpected token 'a', "a" is not valid JSON`
)
await Util.expectParseFailure(
schema,
"{",
Util.isBun
? `JSON Parse error: Expected '}'`
: `Expected property name or '}' in JSON at position 1`
)
})

it("encoding", async () => {
const schema = S.ParseJson
await Util.expectEncodeSuccess(schema, "a", `"a"`)
await Util.expectEncodeSuccess(schema, { a: "b" }, `{"a":"b"}`)

const bad: any = { a: 0 }
bad["a"] = bad
await Util.expectEncodeFailure(
schema,
bad,
Util.isBun ?
`JSON.stringify cannot serialize cyclic structures.` :
`Converting circular structure to JSON
it("encoding", async () => {
const schema = S.parseJson()
await Util.expectEncodeSuccess(schema, "a", `"a"`)
await Util.expectEncodeSuccess(schema, { a: "b" }, `{"a":"b"}`)

const bad: any = { a: 0 }
bad["a"] = bad
await Util.expectEncodeFailure(
schema,
bad,
Util.isBun ?
`JSON.stringify cannot serialize cyclic structures.` :
`Converting circular structure to JSON
--> starting at object with constructor 'Object'
--- property 'a' closes the circle`
)
)
})
})

describe("parseJson(schema)", () => {
it("decoding", async () => {
const schema = S.parseJson(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("encoding", async () => {
const schema = S.parseJson(S.struct({ a: S.number }))
await Util.expectEncodeSuccess(schema, { a: 1 }, `{"a":1}`)
})
})

it("compose", async () => {
const schema = S.ParseJson.pipe(S.compose(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"`)
await Util.expectEncodeSuccess(schema, { a: 1 }, `{"a":1}`)
describe("parseJson(schema, options)", () => {
it("reviver", async () => {
const schema = S.parseJson(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("replacer", async () => {
const schema = S.parseJson(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.parseJson(S.struct({ a: S.number }), { space: 2 })
await Util.expectEncodeSuccess(
schema,
{ a: 1 },
`{
"a": 1
}`
)
})
})
})

0 comments on commit 6d85ae1

Please sign in to comment.