JSON Schema is a very useful tool for validating API requests. Sadly it’s not immune to the quagmire of pedantry that is URL validation.

Consider the following request payload.

{
  "data": { "callbackUrl": "..." }
}

You may be tempted to validate this payload against the following JSON Schema.

{
  "$id": "https://www.stephenlewis.me/nope.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": "data",
  "properties": {
    "data": {
      "type": "object",
      "required": "callbackUrl",
      "properties": {
        "callbackUrl": {
          "type": "string",
          "format": "uri"
        }
      }
    }
  }
}

This works fine, if you agree that dap://[2001:db8::7]/c=GB?objectClass?one is a valid callback URL. I do not. I want something along the lines of https://www.example.com/endpoint.

Here’s how to do that[1].

{
  "$id": "https://www.stephenlewis.me/sane-url.json",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": "data",
  "properties": {
    "data": {
      "type": "object",
      "required": "callbackUrl",
      "properties": {
        "callbackUrl": { "$ref": "#/definitions/saneUrl" }
      }
    }
  },
  "definitions": {
    "saneUrl": { "format": "uri", "pattern": "^https?://" }
  }
}

Scheme-less URLs

But what about “scheme-less” URLs, such as www.example.com?

This is where JSON Schema comes up short. If you’re dealing with just a domain, and no path, the following definition covers the basics. It fails miserably on something like www.example.com/endpoint, though.

{
  "definitions": {
    "plausibleUrl": {
      "anyOf": [{ "$ref": "#/definitions/saneUrl" }, { "format": "hostname" }]
    }
  }
}

Either way, you’ll still need to do some manual validation.


  1. Sort of. Madness such as https://[a1:b2::c3]/omg=WHY will still pass, so you may wish to perform further validation. ↩︎