Zaker
Zaker

Reputation: 537

How to properly define an object within definition and reference in multiple places for Json Schema

I am trying to create a json schema wherein I have an object within a definition & this definition is called within multiple places. I see an error saying UnhandledPromiseRejectionWarning: Error: duplicate type name: Location

I have the below code.

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "definitions": {
    "holiday": {
      "description": "A collection of time off associated with the employee.",
      "required": [],
      "properties": {
        "location": {
          "type": "object",
          "nullable": true,
          "title": "Location",
          "properties": {
            "city": {
              "type": "string",
              "nullable": true,
              "tsType": "string | null",
              "description": ""
            }
          }
        }
      },
      "type": "object"
    }

  },
  "description": "The model for the employee object received on the Ingress API.",
  "properties": {
    "eventType": {
      "avroType": "string",
      "enum": ["EMPLOYEE_TIMEOFF_CREATED", "EMPLOYEE_CREATED_OR_UPDATED"],
      "tsEnumNames": ["EmployeeTimeOffCreated", "EmployeeCreatedOrUpdated"],
      "type": "string"
    },
    "employeeCreatedOrUpdated": {
      "description": "Event data for a employee create request.",
      "required": ["code", "firstName", "lastName"],
      "properties": {
        "code": {
          "description": "A unique code for an employee.",
          "minLength": 1,
          "type": "string"
        },
        "firstName": {
          "description": "This field describes the first name of the employee.",
          "minLength": 1,
          "type": "string"
        },
        "middleName": {
          "description": "This field describes the middle name of the employee.",
          "nullable": true,
          "tsType": "string | null",
          "type": "string"
        },
        "lastName": {
          "description": "This field describes the last name of the employee.",
          "minLength": 1,
          "type": "string"
        },
        "timeOff": {
          "description": "A collection of employee time off associated with the employee.",
          "items": {
            "$ref": "#/definitions/holiday"
          },
          "nullable": true,
          "type": "array"
        }
      },
      "title": "EmployeeCreatedOrUpdated",
      "type": "object"
    },

    "employeeTimeOffCreated": {
      "description": "Event data for an employee time off created request.",
      "required": ["timeOffCreated", "employeeCode"],
      "$id": "https://io.something/v2/employee/employeeTimeOffCreated",
      "properties": {
        "timeOffCreated": {
          "$ref": "#/definitions/holiday"
        },
        "employeeCode": {
          "description": "A unique code for an employee.",
          "minLength": 1,
          "type": "string"
        }
      },
      "title": "EmployeeTimeOffCreated",
      "type": "object"
    }
  },
  "required": ["eventType"],
  "title": "EmployeeEvent",
  "type": "object"
}

So, I use the holiday definition at two places. I tried to have an id within $ref but that doesn't work. Any help is highly appreciated. Thanks.

Upvotes: 0

Views: 1102

Answers (1)

Jason Desrosiers
Jason Desrosiers

Reputation: 24489

Here's your schema reduced to only the parts needed to understand the problem.

{
  "$schema": "http://json-schema.org/draft-07/schema",

  "type": "object",
  "properties": {
    "employeeCreatedOrUpdated": {
      "type": "object",
      "properties": {
        "timeOff": {
          "type": "array",
          "items": { "$ref": "#/definitions/holiday" }
        }
      }
    },
    "employeeTimeOffCreated": {
      "$id": "https://io.something.ingress/v2/employee/employeeTimeOffCreated",
      "type": "object",
      "properties": {
        "timeOffCreated": { "$ref": "#/definitions/holiday" }
      }
    }
  }

  "definitions": {
    "holiday": true
  }
}

When you use $id in a sub-schema like this, it indicates that the sub-schema is a completely separate schema embedded in the parent schema. Any references inside of the embedded schema are relative the the $id, not the parent schema. So, the reference at "timeOffCreated" expects #/definitions/holiday relative to the embedded schema. There's nothing there, so you get an error.

If you don't need "employeeTimeOffCreated" to be an embedded schema, the easiest thing is to remove $id and your references will work. Otherwise, you can give /definitions/holiday an $id as well and reference with that URI instead.

Embedded schemas are really only good for bundling schemas for distribution. Otherwise, you probably want to maintain separate schemas for each of your entities and just reference other schemas when you need them.

Here's what it would look like neatly bundled. If you want to work on these as separate schemas as suggested, you just need to extract each of the definitions into their own schema.

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "$id": "https://io.something.ingress/v2/employee",

  "type": "object",
  "properties": {
    "employeeCreatedOrUpdated": {
      "type": "object",
      "properties": {
        "timeOff": {
          "type": "array",
          "items": { "$ref": "./holiday" }
        }
      }
    },
    "employeeTimeOffCreated": { "$ref": "./employeeTimeOffCreated" }
  }

  "definitions": {
    "employeeTimeOffCreated": {
      "$id": "./employeeTimeOffCreated",
      "type": "object",
      "properties": {
        "timeOffCreated": { "$ref": "./holiday" }
      }
    },
    "holiday": {
      "$id": "./holiday",
      ...
    }
  }
}

Additional reference: https://json-schema.org/understanding-json-schema/structuring.html#bundling

Upvotes: 1

Related Questions