AnyVali Product Overview¶
What Is AnyVali?¶
AnyVali is a family of native validation libraries that share a single portable schema model across 10 programming languages. Unlike language-neutral schema DSLs, AnyVali lets you write schemas directly in your host language using a small, idiomatic API. Each SDK can then export those schemas to a canonical JSON document and import them into any other supported SDK.
The supported languages are:
- JavaScript / TypeScript
- Python
- Go
- Java
- C#
- Rust
- PHP
- Ruby
- Kotlin
- C++
AnyVali occupies a unique position: it gives you the ergonomics of native schema builders (like Zod for JS or pydantic for Python) with the cross-language portability of JSON Schema.
Core Concepts¶
Native-First Authoring¶
Schemas are authored in the host language, not in a separate DSL. Each SDK exposes builder functions that feel natural to developers in that ecosystem. The API surface is small and consistent across SDKs while respecting language idioms.
For example, a JS developer uses camelCase methods, a Python developer uses snake_case, and a Go developer uses exported PascalCase functions. The semantics are identical; the naming adapts.
Portable Export and Import¶
Any schema built with an AnyVali SDK can be exported to a canonical JSON document. That document can then be imported into any other AnyVali SDK. This enables:
- Sharing validation rules between a TypeScript frontend and a Go backend
- Storing schemas in a database and loading them at runtime in any language
- Building schema registries that serve multiple services in different languages
- Generating documentation from schemas regardless of the authoring language
Export operates in two modes:
- Portable mode -- fails if the schema depends on non-portable features. This is the safe default.
- Extended mode -- emits the core schema plus language-specific extension namespaces.
Safe Numeric Defaults¶
AnyVali defaults number to IEEE 754 float64 and int to signed int64. This is a deliberate choice that prioritizes cross-language safety over memory efficiency. See the Numeric Semantics Guide for the full rationale.
Quick Start¶
JavaScript / TypeScript¶
import { v } from "anyvali";
// 1. Define a schema
const UserSchema = v.object({
id: v.int64(),
name: v.string().minLength(1).maxLength(100),
email: v.string().format("email"),
age: v.int().min(0).max(150).optional(),
role: v.enum(["admin", "user", "guest"]).default("user"),
});
// 2. Parse input (throws on failure)
const user = UserSchema.parse({
id: 42,
name: "Alice",
email: "alice@example.com",
});
// => { id: 42, name: "Alice", email: "alice@example.com", role: "user" }
// 3. Handle errors with safeParse
const result = UserSchema.safeParse({ id: "not-a-number", name: "" });
if (!result.success) {
for (const issue of result.issues) {
console.log(`${issue.path.join(".")}: [${issue.code}] ${issue.message}`);
// "id: [invalid_type] Expected int64, received string"
// "name: [too_small] String must have at least 1 character"
}
}
// 4. Export to portable JSON
const doc = UserSchema.export({ mode: "portable" });
console.log(JSON.stringify(doc, null, 2));
// {
// "anyvaliVersion": "1.0",
// "schemaVersion": "1",
// "root": {
// "kind": "object",
// "properties": {
// "id": { "kind": "int64" },
// "name": { "kind": "string", "minLength": 1, "maxLength": 100 },
// ...
// },
// ...
// },
// "definitions": {},
// "extensions": {}
// }
// 5. Import from JSON
import { importSchema } from "anyvali";
const imported = importSchema(doc);
const parsed = imported.parse({ id: 1, name: "Bob", email: "bob@test.com" });
Python¶
import anyvali as v
# 1. Define a schema
user_schema = v.object({
"id": v.int64(),
"name": v.string().min_length(1).max_length(100),
"email": v.string().format("email"),
"age": v.int().min(0).max(150).optional(),
"role": v.enum(["admin", "user", "guest"]).default("user"),
})
# 2. Parse input (raises on failure)
user = user_schema.parse({
"id": 42,
"name": "Alice",
"email": "alice@example.com",
})
# => {"id": 42, "name": "Alice", "email": "alice@example.com", "role": "user"}
# 3. Handle errors with safe_parse
result = user_schema.safe_parse({"id": "not-a-number", "name": ""})
if not result.success:
for issue in result.issues:
print(f"{'.'.join(str(p) for p in issue.path)}: [{issue.code}] {issue.message}")
# 4. Export to portable JSON
import json
doc = user_schema.export(mode="portable")
print(json.dumps(doc, indent=2))
# 5. Import from JSON
imported = v.import_schema(doc)
parsed = imported.parse({"id": 1, "name": "Bob", "email": "bob@test.com"})
Go¶
package main
import (
"encoding/json"
"fmt"
"log"
v "github.com/BetterCorp/AnyVali-go"
)
func main() {
// 1. Define a schema
userSchema := v.Object(v.Fields{
"id": v.Int64(),
"name": v.String().MinLength(1).MaxLength(100),
"email": v.String().Format("email"),
"age": v.Int().Min(0).Max(150).Optional(),
"role": v.Enum("admin", "user", "guest").Default("user"),
})
// 2. Parse input
input := map[string]any{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
}
user, err := userSchema.Parse(input)
if err != nil {
log.Fatal(err)
}
fmt.Println(user)
// 3. Handle errors with SafeParse
result := userSchema.SafeParse(map[string]any{
"id": "not-a-number",
"name": "",
})
if !result.Success {
for _, issue := range result.Issues {
fmt.Printf("%s: [%s] %s\n",
issue.Path, issue.Code, issue.Message)
}
}
// 4. Export to portable JSON
doc, err := userSchema.Export(v.ExportPortable)
if err != nil {
log.Fatal(err)
}
jsonBytes, _ := json.MarshalIndent(doc, "", " ")
fmt.Println(string(jsonBytes))
// 5. Import from JSON
imported, err := v.ImportSchema(doc)
if err != nil {
log.Fatal(err)
}
parsed, err := imported.Parse(map[string]any{
"id": 1, "name": "Bob", "email": "bob@test.com",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(parsed)
}
Architecture Overview¶
Schema Kinds¶
AnyVali v1 defines the following portable schema kinds:
| Category | Kinds |
|---|---|
| Special | any, unknown, never |
| Primitives | null, bool, string |
| Numeric | number (float64), int (int64), float32, float64, int8, int16, int32, int64, uint8, uint16, uint32, uint64 |
| Literal / Enum | literal, enum |
| Collections | array, tuple, object, record |
| Composition | union, intersection |
| Modifiers | optional, nullable |
| Reference | ref |
Validation Constraints¶
Constraints are declarative and data-only. No executable code is serialized.
- String:
minLength,maxLength,pattern,startsWith,endsWith,includes,format - Numeric:
min,max,exclusiveMin,exclusiveMax,multipleOf - Array:
minItems,maxItems - Object: required/optional fields, unknown key mode (
reject,strip,allow)
Parse Pipeline¶
Every SDK follows the same five-step parse pipeline:
Input
|
v
1. Detect presence or absence
|
v
2. If present and coercion configured, attempt coercion
|
v
3. If absent and default exists, materialize default
|
v
4. Validate resulting value against schema constraints
|
v
5. Return parsed output or structured error
This ordering is deterministic across all SDKs. Coercions run on present values before defaults fill in missing ones. The final value -- whether original, coerced, or defaulted -- must pass all validation constraints.
Parse Result¶
Every SDK provides two parse APIs:
- Throwing/panicking parse: returns the parsed value or raises an error.
- Safe parse: returns a result object containing either the parsed value or a list of issues.
Issues follow a standard structure:
{
"code": "invalid_type",
"message": "Expected int64, received string",
"path": ["users", 0, "id"],
"expected": "int64",
"received": "string"
}
Portable JSON Document¶
Schemas export to a versioned JSON document:
{
"anyvaliVersion": "1.0",
"schemaVersion": "1",
"root": { ... },
"definitions": { ... },
"extensions": { ... }
}
rootcontains the top-level schema node.definitionsholds named schema nodes for reuse and recursion (referenced via{ "kind": "ref", "ref": "#/definitions/Name" }).extensionscontains namespaced, language-specific metadata.
Comparison with Similar Tools¶
vs Zod (JavaScript)¶
Zod is a TypeScript-first schema validation library. AnyVali shares Zod's builder-style API design but differs in key ways:
- Zod is JS/TS only. AnyVali targets 10 languages.
- Zod schemas are not serializable. AnyVali schemas export to portable JSON.
- Zod supports arbitrary transforms and refinements. AnyVali v1 limits transforms to portable coercions.
- Zod has no cross-language interop story. AnyVali is built for it.
vs Valibot (JavaScript)¶
Valibot is a modular, tree-shakeable JS validation library. Compared to AnyVali:
- Valibot optimizes for JS bundle size via modular imports. AnyVali optimizes for cross-language portability.
- Valibot is JS/TS only. AnyVali spans 10 languages.
- Valibot schemas are not serializable to a portable format.
vs ArkType (TypeScript)¶
ArkType uses TypeScript's type system for schema inference. Compared to AnyVali:
- ArkType is deeply coupled to TypeScript's type system. AnyVali is language-agnostic.
- ArkType offers powerful type-level inference. AnyVali focuses on runtime validation and portability.
- ArkType schemas are not portable across languages.
vs JSON Schema¶
JSON Schema is a specification for describing JSON data formats. Compared to AnyVali:
- JSON Schema is authoring-language-neutral (you write JSON or YAML). AnyVali lets you write in your host language.
- JSON Schema has a very large specification surface. AnyVali has a deliberately small core.
- JSON Schema does not define a parse pipeline or result format. AnyVali specifies exact parse semantics.
- JSON Schema does not include coercions or defaults as first-class concepts. AnyVali does.
- JSON Schema validators vary in behavior across implementations. AnyVali mandates a conformance test suite.
- AnyVali's portable JSON format is simpler and more opinionated than JSON Schema, which makes cross-SDK consistency achievable.
Next Steps¶
- Numeric Semantics Guide -- understand AnyVali's numeric type system
- Portability Guide -- design schemas that work across languages
- SDK Authors Guide -- implement a new AnyVali SDK