A
arktype3w ago
Robb

Discriminated unions with common field but different constraints

Hello, I am having trouble setting up a schema-system with my types. My code is type-first, so the schemas must be written in accordance to my types. My entities have a type property, which should be the discriminating property. Two types are possible: typeA and typeB. Both types of entities have a field named someField. By default, someField is nullable, meaning new types in the future should have it nullable. But typeA entities require someField not to be null. Creating the corresponding schemas lead to a type error when trying to match my types to the schema's types. Here is my code: types.ts
interface BaseEntity {
id: number;
name: string;
someField: string | null; // By default, someField can be null
}

interface EntityA extends BaseEntity {
type: "typeA";
someField: string; // someField cannot be null for typeA
}

interface EntityB extends BaseEntity {
type: "typeB";
}


type MyEntity = EntityA | EntityB;

export default MyEntity;
interface BaseEntity {
id: number;
name: string;
someField: string | null; // By default, someField can be null
}

interface EntityA extends BaseEntity {
type: "typeA";
someField: string; // someField cannot be null for typeA
}

interface EntityB extends BaseEntity {
type: "typeB";
}


type MyEntity = EntityA | EntityB;

export default MyEntity;
schema.ts
import { type } from "arktype"

const BaseSchema = type({
name: "string",
someField: "string | null",
});

const ASchema = BaseSchema.and({
type: "'typeA'",
someField: "string",
});

const BSchema = BaseSchema.and({
type: "'typeB'",
});


export const MySchema = ASchema.or(BSchema);
export type MySchemaType = typeof MySchema.infer;
import { type } from "arktype"

const BaseSchema = type({
name: "string",
someField: "string | null",
});

const ASchema = BaseSchema.and({
type: "'typeA'",
someField: "string",
});

const BSchema = BaseSchema.and({
type: "'typeB'",
});


export const MySchema = ASchema.or(BSchema);
export type MySchemaType = typeof MySchema.infer;
1 Reply
Robb
RobbOP3w ago
myFormComponent.tsx
import React from "react";
import {useForm, DefaultValues} from "react-hook-form"

import MyEntity from "./types";
import { MySchemaType } from "./schema";

interface useEntityFormProps {
defaultValues?: DefaultValues<MySchemaType>;
}

const useEntityForm = ({ defaultValues }: useEntityFormProps) => {
const methods = useForm<MySchemaType>({
defaultValues: defaultValues,
});

return methods;
}

interface MyFormComponentProps {
defaultValues?: Omit<MyEntity, "id">;
}

const MyFormComponent = ({ defaultValues }: MyFormComponentProps) => {
const methods = useEntityForm({ defaultValues }); // Here is the type error: Type '"typeA"' is not assignable to type '"typeB"'

return (
<>My Form</>
);
};
import React from "react";
import {useForm, DefaultValues} from "react-hook-form"

import MyEntity from "./types";
import { MySchemaType } from "./schema";

interface useEntityFormProps {
defaultValues?: DefaultValues<MySchemaType>;
}

const useEntityForm = ({ defaultValues }: useEntityFormProps) => {
const methods = useForm<MySchemaType>({
defaultValues: defaultValues,
});

return methods;
}

interface MyFormComponentProps {
defaultValues?: Omit<MyEntity, "id">;
}

const MyFormComponent = ({ defaultValues }: MyFormComponentProps) => {
const methods = useEntityForm({ defaultValues }); // Here is the type error: Type '"typeA"' is not assignable to type '"typeB"'

return (
<>My Form</>
);
};
The discriminant JSON looks correct:
{
"kind": "unit",
"path": ["type"],
"cases": {
"\"typeA\"": {
"required": [
{
"key": "name",
"value": {
"domain": "string",
"pattern": [
{
"flags": "",
"rule": "^(?:(?!^-0$)-?(?:(?:0|[1-9]\\d*)))$",
"meta": "a well-formed integer string"
}
]
}
},
{ "key": "someField", "value": "string" }
]
},
"\"typeB\"": {
"required": [
{
"key": "name",
"value": {
"domain": "string",
"pattern": [
{
"flags": "",
"rule": "^(?:(?!^-0$)-?(?:(?:0|[1-9]\\d*)))$",
"meta": "a well-formed integer string"
}
]
}
},
{ "key": "someField", "value": ["string", { "unit": null }] }
]
}
}
}
{
"kind": "unit",
"path": ["type"],
"cases": {
"\"typeA\"": {
"required": [
{
"key": "name",
"value": {
"domain": "string",
"pattern": [
{
"flags": "",
"rule": "^(?:(?!^-0$)-?(?:(?:0|[1-9]\\d*)))$",
"meta": "a well-formed integer string"
}
]
}
},
{ "key": "someField", "value": "string" }
]
},
"\"typeB\"": {
"required": [
{
"key": "name",
"value": {
"domain": "string",
"pattern": [
{
"flags": "",
"rule": "^(?:(?!^-0$)-?(?:(?:0|[1-9]\\d*)))$",
"meta": "a well-formed integer string"
}
]
}
},
{ "key": "someField", "value": ["string", { "unit": null }] }
]
}
}
}
It seems to be related to the fact that someField is nullable. If I remove | null from the type definition, the type error disappears. My actual entities have nested fields and subtypes with multiple common fields that have different requirements. Hence, is this the wrong way to define the schemas ?

Did you find this page helpful?