Schema.Struct
from Schema (effect)
https://effect.website/docs/schema/basic-usage/#structs
Schema.tagを使ってTagged Unionにする
あるいは、Schema.TaggedStructを使う
単にSchema.Literalでtagを作るのとの違い
code:ts
import { Schema } from "effect"
const Circle1 = Schema.Struct({
_tag: Schema.Literal("circle"),
radius: Schema.Number
})
const Circle2 = Schema.Struct({
_tag: Schema.tag('circle'),
radius: Schema.Number
})
const Circle3 = Schema.TaggedStruct('circle', {
radius: Schema.Number
})
console.log(Circle1.make({ _tag: "circle", radius: 10 }))
console.log(Circle2.make({ radius: 10 }))
console.log(Circle3.make({ radius: 10 }))
console.log(Schema.decodeUnknownSync(Circle1)({ _tag: "circle", radius: 10 }))
console.log(Schema.decodeUnknownSync(Circle2)({ _tag: "circle", radius: 10 }))
console.log(Schema.decodeUnknownSync(Circle3)({ _tag: "circle", radius: 10 }))
makeしたときに、_tagを省略できると言うだけ
Circle2とCircle3は全く同じ
decode時は当然全部必要
Structの拡張
https://effect.website/docs/schema/advanced-usage/#extending-schemas
既存のスキーマに新しいフィールドや型を追加。
方法①: ...Struct.fields を使ってフィールドを追加
code:ts
const Extended = Schema.Struct({
...Original.fields,
c: Schema.String
})
方法②: Schema.extend
より柔軟な構造(ユニオンや複雑な組み合わせ)に対応。
code:ts
const Extended = Schema.extend(BaseStruct, Schema.Union(
Schema.Struct({ b: Schema.String }),
Schema.Struct({ c: Schema.String })
))
この結果、型は a フィールドに加えて b か c のどちらかを持つような構造になる。
🚫 拡張できない例
code:ts
const Struct = Schema.Struct({ a: Schema.String })
const Overlapping = Schema.Struct({ a: Schema.Number }) // 型が競合!
Schema.extend(Struct, Overlapping) // ❌ エラー
a フィールドの型が競合(string vs number)しているため拡張不可。
✅ ブランド付き Refinement の拡張
code:ts
const PositiveInteger = Schema.asSchema(
Schema.extend(
Schema.Positive.pipe(Schema.brand("Positive")),
Schema.Int.pipe(Schema.brand("Int"))
)
)
PositiveInteger は「正の整数」しか許容しないスキーマになる。
✅ 1. プロパティのリネーム
https://effect.website/docs/schema/advanced-usage/#renaming-properties
◼️ 定義時にプロパティ名を変更(fromKey)
code:ts
const schema = Schema.Struct({
a: Schema.propertySignature(Schema.String).pipe(Schema.fromKey("c")),
b: Schema.Number
})
TypeScriptの型(Type) → { a: string; b: number }
エンコード時の型(Encoded) → { c: string; b: number }
つまり、
デコード時に { c: "hoge", b: 1 } を渡すと
スキーマは a: "hoge", b: 1 に変換してくれる。
◼️ オプショナルなプロパティのリネーム
code:ts
a: Schema.optional(Schema.String).pipe(Schema.fromKey("c"))
optional() を使うと自動で propertySignature になるので .propertySignature() は不要。
✅ 2. 既存スキーマのプロパティ名の一括リネーム(Schema.rename)
code:ts
const Original = Schema.Struct({
c: Schema.String,
b: Schema.Number
})
const Renamed = Schema.rename(Original, { c: "a" })
全体的にリネーム可能
Union でも適用できる(Schema.Union(...) の各メンバに対して)
ただし注意点:
Schema.rename は型情報を失う可能性がある(特にネストされた型など)
✅ 3. 再帰スキーマ(Recursive Schema)
https://effect.website/docs/schema/advanced-usage/#recursive-schemas
単純な自己参照
Schema.suspend
code:ts
interface Category {
name: string;
subcategories: ReadonlyArray<Category>;
}
const Category = Schema.Struct({
name: Schema.String,
subcategories: Schema.Array(
Schema.suspend((): Schema.Schema<Category> => Category)
)
})
Schema.suspend を使うと自己参照できる。
型の自己参照になるため、interface を別で定義して型推論を助ける。
interface と Schema を分けるパターン
code:ts
const fields = { name: Schema.String }
interface Category extends Schema.Struct.Type<typeof fields> {
subcategories: ReadonlyArray<Category>
}
const Category = Schema.Struct({
...fields,
subcategories: Schema.Array(
Schema.suspend((): Schema.Schema<Category> => Category)
)
})
fields を分けることで、再帰部だけ suspend にすればよくなり、型定義の重複を減らせる。
✅ 4. 相互再帰(Mutually Recursive)
code:ts
interface Expression {
type: "expression"
value: number | Operation
}
interface Operation {
type: "operation"
operator: "+" | "-"
left: Expression
right: Expression
}
Expression が Operation を、Operation が Expression を参照
両方に Schema.suspend を使うことで相互再帰を可能にしている。
✅ 5. Type と Encoded の型が異なる再帰スキーマ
たとえば以下のようなスキーマ:
code:ts
id: Schema.NumberFromString
Encoded: string
Type: number
この場合、suspend に渡す戻り値型を明示的に書く必要がある:
code:ts
Schema.suspend(
(): Schema.Schema<Category, CategoryEncoded> => Category
)
つまり:
Category → デコード後の「実際に使う型」
CategoryEncoded → デコード前の入力型(JSONなど)
💡 補足:なぜ Schema.suspend が必要か?
TypeScript の型推論は「その定義の中で自分自身を参照する」ことに弱い。
Schema.suspend(() => Category) にすることで、「あとで評価する」ようにして再帰可能にする。