arktype icon indicating copy to clipboard operation
arktype copied to clipboard

Allow null to be treated as not-present for optional fields

Open trsaunders opened this issue 1 year ago • 4 comments

Request a feature

(Related to https://github.com/arktypeio/arktype/issues/1191)

I wish to be able to have a type with an optional non-null field that accepts null as being equivalent to not present:

import { scope } from "arktype";

export const mod = scope({
    Model: {
        "field?": "number.integer",
        other: "string",
    },
});

export const types = mod.export();
export const Model = types.Model;

// should not give an error
console.log(Model(JSON.parse('{"other": "hi", "field": null}')).summary);

🤷 Motivation

I use arktype models to validate data produced by pydantic models. Optional fields in pydantic are, by default, emitted as "field": null when JSON encoded. I wish to have typescript types and validations that accept "field": null as if it were absent from the JSON. It is undesirable to have the field defined as number.intger | null as it adds extra complexity to consumption code.

For this use-case undefined is not an option as it cannot be JSON serialised.

💡 Solution

I am not sure what the best interface would look like, but perhaps a field specified as e.g "field?": "number.integer?" could behave in this way? or a global config, or a mode that applies specifically to JSON parsing?

trsaunders avatar Jan 29 '25 20:01 trsaunders

Not sure exactly what the solution will look like yet either, but I want to build something general here that allows certain types to be treated as if they weren't present as opposed to just special-casing null.

For example, devs working with forms may also want an empty string to be treated as not present.

ssalbdivad avatar Jan 29 '25 20:01 ssalbdivad

+1

I'd love to be able to write type.string.optional({ nullable: true }) or something like this, that normalizes null | undefined to undefined.

status: type.string.or(type.null.pipe(() => undefined)).optional(), seems to be a bit long, also the resulting types doesn't look nice.

plehnen avatar Feb 12 '25 14:02 plehnen

@plehnen thank you for the suggestion of using pipe as an intermediate solution. It was on my todo list to investigate this, so you've just saved me some effort 😄

For now I shall use the following form, but I still see significant value in formal support.

    Model: {
        "field?": type("number.integer").or(type.null.pipe(() => undefined)),
        other: "string",
    },

trsaunders avatar Feb 12 '25 15:02 trsaunders

I'd be happy if I could just configure the decoding more tolerant, rather than loosening the entire type constraint in the model.

guersam avatar Jun 10 '25 14:06 guersam