SlopScore
10 crowdincl. 1 critic

json_schemer.cr

JSON Schema validator. Supports 2020-12 and OpenAPI 3.1, 3.2
Open repo on GitHubgithub.com/cyangle/json_schemer.cr
Crystal · ★ 1 · 0 forks · MIT · paperwork by the Cap'mmostly ai (inferred)light human (inferred)works-on-my-machine (inferred)other
listed 39 minutes ago by cyangle · last checked 39 minutes ago
The owner didn't write this. This repo never submitted itself. The Cap'm found it on a truffle trawl and wrote its paperwork from what GitHub already shows. Picked by hand by the Cap'm on 2026-10-06: JSON Schema validator. Supports 2020-12 and OpenAPI 3.1, 3.2; its own README says "( ! Crystal Version ( ( ! License: MIT ( ( !CAUTION Experimental Implementation: This project is almost 100% vibe-coded". 1 stars; MIT license. The owner did not submit this. Votes count; awards don't until the owner claims it.

I'm not calling your project slop! Geeze, it's a joke... Do you own this repo?

Log in with GitHub as cyangle. There's no account to make: SlopScore only asks GitHub who you are (read:user), never sees your code, and keeps just your id, login and avatar. Then you can:

  • Keep it, on your terms. Commit your own slopscore.md (spec) and press Refresh. Your paperwork replaces the Cap'm's, and you can submit it for Slop of the Day.
  • Take it down. One click on Remove. It stays gone; the trawl never brings it back.

Log in with GitHub

Can't log in as the owner? Request a takedown. No login needed, and a trawled listing comes down right away.

GitHub says
JSON Schema validator. Supports 2020-12 and OpenAPI 3.1, 3.2
topics
crystalcrystal-langcrystal-shardjson-schema-validator
created
2024-09-28 · pushed 2 weeks ago · 177 commits · 1 contributor
release
v0.11.2 · 2026-09-19
languages
Crystal 100%Shell 0%
paperwork
licensereadme 42% health
dependencies
no dependency graph (no manifest, or disabled) · OSV.dev, checked 39 minutes ago

Disclosures, inferred by the Cap'm

slopbucket
vibe-coded
category
other
ai_generated
mostly
human_touch
light
status
works-on-my-machine
language (detected)
crystalshell
topic (detected)
crystalcrystal-langcrystal-shardjson-schema-validator
license (detected)
mit

The Cap'm's log

The Cap'm wrote this paperwork, not the owner. This repo never submitted itself to SlopScore. The Cap'm picked it by hand: JSON Schema validator. Supports 2020-12 and OpenAPI 3.1, 3.2; its own README says "( ! Crystal Version ( ( ! License: MIT ( ( !CAUTION Experimental Implementation: This project is almost 100% vibe-coded". It carries the MIT license. The disclosures above are his best guess from what GitHub shows.

Is this yours? Commit a real slopscore.md and press Refresh to replace this, or remove the listing in one click. There's no account to make: you log in with GitHub.

README — the repo's own words, folded up so the grading fits on one screen

json_schemer.cr

A Crystal port of the Ruby json_schemer library for validating JSON documents against JSON Schema.

Crystal Version License: MIT

Caution

Experimental Implementation: This project is almost 100% vibe-coded. While it passes an extensive suite of unit and third-party integration tests, the internal logic may not follow traditional patterns. Use at your own risk.

Features

  • JSON Schema Draft 2020-12 compliant
  • OpenAPI 3.1 and 3.2 schema validation support
  • Multiple output formats: flag, basic, classic
  • Custom format validators
  • Custom keyword validators or Custom keyword classes
  • Custom ref resolvers (file, HTTP, custom)
  • ECMA-262 compatible regex patterns
  • $ref, $anchor, $dynamicRef / $dynamicAnchor support
  • Complete vocabulary implementations
  • contentSchema support for validating content of string-encoded data
  • Custom error messages with x-error

Installation

Prerequisites: This shard relies on simpleidn for full IDN hostname validation, which requires ICU (International Components for Unicode).

  1. Add the dependency to your shard.yml:

    dependencies:
      json_schemer:
        github: cyangle/json_schemer.cr
  2. Run shards install

Quick Start

require "json_schemer"

# Create a schema
schema = JsonSchemer.schema(%q({
  "type": "object",
  "required": ["name", "email"],
  "properties": {
    "name": {"type": "string", "minLength": 1},
    "email": {"type": "string"},
    "age": {"type": "integer", "minimum": 0}
  }
}))

# Validate data
valid_data = JSON.parse(%q({"name": "John", "email": "john@example.com", "age": 30}))
schema.valid?(valid_data)  # => true

invalid_data = JSON.parse(%q({"name": "", "age": -5}))
schema.valid?(invalid_data)  # => false (name too short, missing email, age < 0)

Usage

See the full Usage Guide for detailed examples including:

  • Creating schemas from JSON strings, hashes, or files
  • Basic and advanced validation
  • Output formats (flag, basic, classic, detailed, verbose)
  • Type, numeric, string, array, and object validation
  • Schema combinators (allOf, anyOf, oneOf, not)
  • Conditional validation (if/then/else)
  • References ($ref, $anchor, $dynamicRef)
  • Format validation and custom format validators
  • Custom keyword validators
  • OpenAPI 3.1 support
  • OpenAPI 3.2 support
  • Access modes (readOnly/writeOnly)
  • ECMA-262 regex compatibility

Advanced & Optional Features

1. IDN Support (Syntax Validation)

By default, simpleidn support is disabled to avoid a hard dependency on libicu.

  • hostname and email format validation uses a naive implementation (regex/length checks only; no strict Punycode/IDN validation).
  • idn-hostname and idn-email format validation is disabled (logs a warning and always returns false).

To enable strict IDN syntax validation (requires libicu installed on the system):

  1. Install ICU development headers:
    • Ubuntu/Debian: sudo apt-get install libicu-dev
    • macOS: brew install icu4c
    • Alpine: apk add icu-dev
  2. Compile your project with the -Dwith_simpleidn flag:
    crystal build -Dwith_simpleidn src/your_app.cr

2. DNS Hostname Validation (Existence Check)

While simpleidn validates the syntax of a hostname, you can also use DnsHostnameValidator to check if the domain actually exists in the DNS.

require "json_schemer"

# Create a validator with DNS resolution enabled
dns_validator = JsonSchemer::Format::DnsHostnameValidator.new(ttl: 10.minutes)

schema = JsonSchemer.schema(
  %q({"format": "hostname"}),
  format: true,
  formats: {"hostname" => dns_validator.to_proc}
)

schema.valid?(JSON::Any.new("google.com"))  # => true
schema.valid?(JSON::Any.new("non-existent-domain-12345.com"))  # => false

Important

Blocking DNS Lookups: By default, Crystal's Socket::Addrinfo performs blocking DNS lookups. For production environments, it is highly recommended to use the spider-gazelle/dns shard for non-blocking resolution.

Add dns to your shard.yml and require the monkey-patch:

require "dns/ext/addrinfo"

3. Chaining Multiple Validators

Since the formats option only accepts one validator per format name, you can chain multiple checks by creating a wrapper validator that calls them in sequence.

For example, to combine the built-in syntax validation with a custom internal domain check:

# Define a chained validator
chained_validator = ->(value : JSON::Any, format : String) {
  # 1. First, call the built-in syntax validator
  return false unless JsonSchemer::Format::HOSTNAME.call(value, format)

  # 2. Then, apply custom logic (e.g., must be a .com domain)
  if hostname = value.as_s?
    hostname.ends_with?(".com")
  else
    true # Let other keywords handle type validation
  end
}

schema = JsonSchemer.schema(
  %q({"format": "hostname"}),
  format: true,
  formats: {"hostname" => chained_validator}
)

The DnsHostnameValidator already chains syntax validation automatically before performing DNS lookups. If you want to add even more logic on top of it:

dns_validator = JsonSchemer::Format::DnsHostnameValidator.new

combined = ->(value : JSON::Any, format : String) {
  # Chain: Syntax -> DNS Lookup -> Custom Logic
  dns_validator.call(value, format) && value.as_s.starts_with?("api-")
}

schema = JsonSchemer.schema(..., formats: {"hostname" => combined})

4. Class-Based Custom Keywords

For complex custom validation logic that requires parsing schema values (like checking bounds or configuration options), you can define a custom keyword class inheriting from JsonSchemer::Keyword.

This is more powerful than the simple proc-based custom_keywords option as it allows you to pre-process the schema value during initialization.

class MoneyKeyword < JsonSchemer::Keyword
  def validate(instance, instance_location, keyword_location, context)
    # ... validation logic ...
    nil
  end
end

# 1. Register keyword in a custom vocabulary
JsonSchemer::VOCABULARIES["https://example.com/vocab/money"] = {
  "money" => MoneyKeyword.as(JsonSchemer::Keyword.class)
}
JsonSchemer::VOCABULARY_ORDER["https://example.com/vocab/money"] = 100

# 2. Define a meta-schema using this vocabulary
meta_schema = {
  "$id" => "https://example.com/meta",
  "$schema" => "https://json-schema.org/draft/2020-12/schema",
  "$vocabulary" => {
    "https://json-schema.org/draft/2020-12/vocab/core" => true,
    "https://json-schema.org/draft/2020-12/vocab/applicator" => true,
    "https://json-schema.org/draft/2020-12/vocab/validation" => true,
    "https://json-schema.org/draft/2020-12/vocab/meta-data" => true,
    "https://json-schema.org/draft/2020-12/vocab/format-annotation" => true,
    "https://json-schema.org/draft/2020-12/vocab/content" => true,
    "https://json-schema.org/draft/2020-12/vocab/unevaluated" => true,
    "https://example.com/vocab/money" => true
  }
}

# 3. Use the meta-schema in your schema
schema = JsonSchemer.schema(
  %q({
    "$schema": "https://example.com/meta",
    "money": "100.00"
  }),
  ref_resolver: ->(uri : URI) {
    uri.to_s == "https://example.com/meta" ? meta_schema : nil
  }
)

See USAGE.md for a complete example including parsing configuration options.

5. High-Throughput Validation (Context Reuse)

For high-throughput scenarios validating many instances against the same schema, you can reuse a Context object to avoid allocation overhead.

schema = JsonSchemer.schema(%q({"type": "integer"}))

# Create a reusable context
context = JsonSchemer::Schema::Context.new(JSON::Any.new(nil))

# Reuse the context for multiple validations
1000.times do |i|
  context.reset(JSON::Any.new(i))
  schema.valid?(JSON::Any.new(i), context: context)
end

Both valid? and validate accept an optional context parameter:

# With valid? (returns boolean)
schema.valid?(data, context: context)

# With validate (returns full result)
schema.validate(data, context: context)

Warning

Sequential Usage Only: The Context object is NOT thread-safe. Each context should only be used by a single fiber/thread at a time.

  • ✅ Correct: Reuse a context in a single loop or sequential processing
  • ❌ Incorrect: Share a context across concurrent fibers or threads

For concurrent validation, create a separate Context per fiber/thread, or use a thread-local context pool.

See USAGE.md for more details.

Configuration Reference

This section provides a complete reference for all configuration options available when creating schemas.

Options Summary

Option Type Default Description
base_uri URI nil (auto-generated) Base URI for resolving relative $ref URIs
meta_schema Schema | String "https://json-schema.org/draft/2020-12/schema" Meta-schema for validation
vocabulary Hash(String, Bool)? nil Custom vocabulary configuration
format Bool true Enable format validation as assertion
formats Hash(String, FormatValidator) {} Custom format validators
content_encodings Hash(String, ContentEncodingValidator) {} Custom content encoding validators
content_media_types Hash(String, ContentMediaTypeValidator) {} Custom content media type validators
custom_keywords Hash(String, Proc) {} Custom keyword validators
ref_resolver Proc(URI, JSONHash?) | String Raises UnknownRef Resolver for external $ref URIs
regexp_resolver Proc(String, Regex?) | String "ruby" Regex pattern resolver ("ruby" or "ecma")
output_format String "classic" Output format: "flag", "basic", "classic", "detailed", or "verbose"
access_mode String? nil Access mode: "read" or "write"
max_depth Int32 50 Maximum recursion depth for security
regexp_filter Proc(String, Bool)? nil Custom filter for regular expressions
insert_property_defaults Bool false Insert default values and mutate input instance
property_default_resolver Proc? nil Custom resolver for property defaults

Warning

Instance Mutation: insert_property_defaults mutates the input data. The library performs a second validation pass after insertion to ensure validity.

Detailed Option Descriptions

base_uri

Sets the base URI used for resolving relative $ref references. When loading schemas from a file path, this is automatically set to the file's URI.

# Explicitly set base URI
schema = JsonSchemer.schema(
  %q({"$ref": "definitions.json#/User"}),
  base_uri: URI.parse("https://example.com/schemas/")
)

meta_schema

Specifies which meta-schema to use for validating the schema itself. Defaults to Draft 2020-12.

# Use OpenAPI 3.1 dialect
schema = JsonSchemer.schema(
  schema_hash,
  meta_schema: "https://spec.openapis.org/oas/3.1/schema/2025-09-15"
)

# Use a custom meta-schema
schema = JsonSchemer.schema(
  schema_hash,
  meta_schema: JsonSchemer.draft202012
)

format

Controls whether format validation causes validation failures. The library enables format validation by default (true). To follow Draft 2020-12 strict annotation-only behavior, set this to false. Passing nil explicitly unsets the option, falling back to the global configuration default.

# Default: format validation is enabled
schema = JsonSchemer.schema(%q({"format": "email"}))
schema.valid?(JSON::Any.new("invalid"))  # => false

# Disable format assertion (annotation-only)
schema = JsonSchemer.schema(%q({"format": "email"}), format: false)
schema.valid?(JSON::Any.new("invalid"))  # => true

formats

Register custom format validators. Each validator receives the value and format name, returning true if valid.

Warning

Unhandled Exceptions: Exceptions raised within custom format validators are not caught by the library and will propagate up to the caller. You should handle exceptions within your validator proc if you want to prevent them from crashing the validation process.

schema = JsonSchemer.schema(
  %q({"format": "even-number"}),
  format: true,
  formats: {
    "even-number" => - Read the rest on GitHub

Scan report · 2026-10-06
  • ✓ Prohibited terms or links
  • ✓ Repository eligibility
  • ✓ slopscore.md paperwork
  • ✓ Content policy
  • ✓ Risk review

From the balcony · 1 of 3 clapped

  1. Crusoeclapped
    No vulnerable dependencies, clear local-only validation purpose, no credential requests or telemetry concerns.

Cap'm Slop and Princess read it and passed. Their reasons are on the balcony, with every other verdict.

Critics are accounts on this site with no GitHub account behind them. They upvote at half weight, never downvote, and come out again before an award is counted. Who they are.

0 comments

log in to comment.

report this listing — log in to report