0
votes

I have the following service object in schema definitions:

"services": {
  "type": "object"
  "propertyNames": {
    "pattern": "^[A-Za-z_]*$",
    "errorMessage": "service names must be non-numeric"
  }
  "patternProperties": {
    "^[A-Za-z_]*$": {
      "type": "object"
      "properties": {
        "source": {
          "type": ["string", "object"]
        },
        "port": {
          "type": "number",
          "errorMessage": "invalid port value"
        },
        "cpu": {
          "type": "number",
          "errorMessage": "invalid cpu value
        },
        "memory": {
          "type": "number",
          "errorMessage": "invalid memory value
        },
        "overrides": { "$ref": "#/definitions/overrides" },
        "allOf": [...]
      }
   }
 }

In a separate definition, I am maintaining the following override object:

      "overrides": {
      "type": "object",
      "propertyNames": {
        "pattern": "^(dev|int|trn|qa|stag|prod)?$",
        "errorMessage": "override stage name must be approved environment (dev, int, trn, qa, stag, prod)"
       },
      "patternProperties": {
        "^(dev|int|trn|qa|stag|prod)?$": {
          "type": "object",
          "propertyNames": {
            "pattern": "^[A-Za-z_]*$",
            "errorMessage": "override property name must be non-numeric"
           },
          "properties": {
            "$ref": "..."
          },
          "allOf": [...]
        }
      }
    }

For some context, this is for a configuration file where a service object is defined with port, source, cpu, mem, and other options. Each service can have an override object which can be used to replace service object properties per environment... e.g.

{
   "services": {
      "main": {
         "source": "https://github.com/foo,
         "port": 3000,
         "cpu": 256,
         "memory": 1,
         "overrides": {
            "prod": {
               "port": 8443
            }
         }
      }
   }
}

My goal is to optimize this so that I do not have to maintain two of the same schemas. Essentially right now every property that exists in service definition is then duplicated as a property of the override objects. I am also maintaining separate "allOf" validations as the service object properties are required, but override object properties are all optional.

Is it possible to leverage the $ref feature in order to point to the properties of service definition from the override definition? Furthermore, would it be possible to get rid of the override schema entirely and reuse the service schema instead, or does the differences in their schema patternProperties prevent that?

I appreciate any guidance that might get me over the hump.

Full schema minus the allOf blocks for brevity:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "services": {
      "type": "object",
      "propertyNames": {
        "pattern": "^[A-Za-z_]*$",
        "errorMessage": "service names must be non-numeric"
      },
      "patternProperties": {
        "^[A-Za-z_]*$": {
          "type": "object",
          "propertyNames": {
            "pattern": "^[A-Za-z_]*$",
            "errorMessage": "service property names must be non-numeric"
           },
          "properties": {
            "source": {
              "type": ["string", "object"]
            },
            "port": {
              "type": "number",
              "errorMessage": "invalid port value"
            },
            "cpu": {
              "type": "number",
              "enum": [256, 512, 1024, 2048, 4096],
              "errorMessage": "invalid cpu value - should be one of the following: 256, 512, 1024, 2048, 4096"
            },
            "memory": {
              "type": "number",
              "enum": [0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
                12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
                22, 23, 24, 25, 26, 27, 28, 29, 30],
              "errorMessage": "memory value should be 0.5 - 30"
            },
            "environment": {
              "type": "object",
              "errorMessage": "invalid environment variables defined  - must be an object"
            },
            "overrides": { "$ref": "#/definitions/overrides" }
          },
          "required": ["source", "port", "cpu", "memory", "min_instances"],
          "errorMessage": {
            "required": {
              "source": "missing service source",
              "port": "missing service port",
              "cpu": "missing service cpu",
              "memory": "missing service memory",
              "min_instances": "missing minimum required service instances"
            },
            "type": "service must have at least one defined property",
            "additionalProperties": "invalid property found in service definition"
          },
          "allOf": [...],
          "additionalProperties": false
        }
      },
      "errorMessage": { "type": "must have at least one defined service" }
    },
    "overrides": {
      "type": "object",
      "propertyNames": {
        "pattern": "^(dev|int|trn|qa|stag|prod)?$",
        "errorMessage": "override stage name must be approved environment (dev, int, trn, qa, stag, prod)"
       },
      "patternProperties": {
        "^(dev|int|trn|qa|stag|prod)?$": {
          "type": "object",
          "propertyNames": {
            "pattern": "^[A-Za-z_]*$",
            "errorMessage": "override property name must be non-numeric"
           },
          "properties": {
            "port": {
              "type": "number",
              "errorMessage": "invalid port value"
            },
            "cpu": {
              "type": "number",
              "enum": [256, 512, 1024, 2048, 4096],
              "errorMessage": "invalid cpu value - should be one of the following: 256, 512, 1024, 2048, 4096"
            },
            "memory": {
              "type": "number",
              "enum": [0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
                12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
                22, 23, 24, 25, 26, 27, 28, 29, 30],
              "errorMessage": "memory value should be 0.5 - 30"
            },
            "environment": {
              "type": "object",
              "errorMessage": "invalid environment variables defined  - must be an object"
            }
          },
          "errorMessage": {
            "type": "environment must have at least one defined override",
            "additionalProperties": "invalid property found in override definition"
          },
          "allOf": [...],
          "additionalProperties": false
        }
      },
      "errorMessage": { "type": "overrides must have at least one defined environment" }
    }
  },
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "errorMessage": "app name should be string"
    },
    "account": {
      "type": "string",
      "errorMessage": "invalid account value"
    },
    "id": {
      "type": "string",
      "errorMessage": "invalid id tag value"
    },
    "services": {
      "$ref": "#/definitions/services"
    },
    "stages": {
      "type": "array",
      "errorMessage": "invalid stages option - must be an array"
    }
  },
  "required": ["name", "account", "id", "services"],
  "errorMessage": {
    "required": {
      "name": "missing app name",
      "account": "missing designated account",
      "id": "missing designated id tag",
      "services": "no services are defined"
    }
  }
}
1
Please provide your full schemas including which draft you're using. I might then be able to help =]Relequestual
Had not noticed I left out the draft! haha I am using draft-07. I can update with the full schema. Should it include the allOf blocks? It has grown quite unrulygirib
Try to include only what is relevant to the question. In this case, I think it all might be relevant.Relequestual
I was editing before I read your comment. I paged through the allOf blocks, and they are identical. As far as relevance goes, would it be safe to assume that the allOf blocks could also be de-duplicated?girib
Yup. Thanks for updatingRelequestual

1 Answers

1
votes

Because you've added fields for some properties which are different, you can't de-duplicate all of it. errorMessage is not part of the JSON Schema specification, so it's use is limited to the library you're using.

Some of the properties you can de-duplicate, and you've already used references ($ref).

You could move the memory component to it's own definition...

...
"definitions": {
    "componentMemory": {
      "type": "number",
      "enum": [0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
        12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
        22, 23, 24, 25, 26, 27, 28, 29, 30],
      "errorMessage": "memory value should be 0.5 - 30"
    },
...

and then reference it in both subschema locations...

...
            "cpu": {
              "type": "number",
              "enum": [256, 512, 1024, 2048, 4096],
              "errorMessage": "invalid cpu value - should be one of the following: 256, 512, 1024, 2048, 4096"
            },
            "memory": {
              "$ref": "#/definitions/services"
            },
...

If you cared less about the error messages, it could be much further de-duplicated, but I'm guessing you do care about them.

As such, anywhere that the subschemas are identical, it can be de-duplicated.

The values of a properties object ARE subschemas.