Intro

Morphs & More

Sometimes, data at the boundaries of your code requires more than validation before it's ready to use.

Morphs allow you to arbitrarily transform the shape and format of your data.

Morphs can be piped before, after or between validators and even chained to other morphs.

// Hover to see the type-level representation
const  = ("string").pipe((s): object => JSON.parse(s))

// object: { ark: "type" }
const  = parseJson('{ "ark": "type" }')

// ArkErrors: must be a string (was object)
const  = parseJson()

This is a good start, but there are still a couple major issues with our morph.

What happens if we pass a string that isn't valid JSON?


// Uncaught SyntaxError: Expected property name ☠️
const  = parseJson('{ unquoted: "keys" }')

Despite what JSON.parse might have you believe, throwing exceptions and returning any are not very good ways to parse a string. By default, ArkType assumes that if one of your morphs or narrows throws, you intend to crash.

If you do happen to find yourself at the mercy of an unsafe API, you might consider wrapping your function body in a try...catch.

Luckily, there is a built-in API for wrapping piped functions you don't trust:

const  = ("string").pipe.try((s): object => JSON.parse(s))

// Now returns an introspectable error instead of crashing 🎉
const  = parseJson('{ unquoted: "keys" }')

const  = parseJson('{ "ark": "type" }')

if ( instanceof type.errors) .throw()
// Unfortunately, a validated `object` still isn't very useful...
else console.log()

The best part about pipe is that since any Type is root-invokable, Types themselves are already morphs! This means validating out parsed output is as easy as adding another pipe:

const  = ("string").pipe.try(
	(s): object => JSON.parse(s),
	({
		name: "string",
		version: "string.semver"
	})
)

const  = parseJson('{ "name": "arktype", "version": "2.0.0" }')

if (!( instanceof type.errors)) {
	// Logs "arktype:2.0.0"
	console.log(`${.name}:${.version}`)
}

At this point, our implementation is starting to look pretty clean, but in many cases like this one, we can skip straight to the punch line with one of ArkType's many built-in aliases for validation and parsing, string.json.parse:

// .to is a sugared .pipe for a single parsed output validator
const  = ("string.json.parse").to({
	name: "string",
	version: "string.semver"
})

const  = parseJson('{ "name": true, "version": "v2.0.0" }')

if ( instanceof type.errors) {
	// hover out.summary to see the default error message
	console.error(.)
}

If you've made it this far, congratulations! You should have all the fundamental intuitions you need to bring your types to runtime ⛵

Our remaining docs will help you understand the trade offs between ArkType's most important APIs so that no matter the application, you can find a solution that feels great to write, great to read, and great to run.