Search code examples
javascriptnode.jsjsonjavascript-objectsjsonschema

Generate an output JSON message from an input JSON message based on an output JSON schema


I have a requirement to write a NodeJS solution and library to receive an input JSON data which the solution must transform into an output JSON data that matches or validates to a provided output JSON schema.

At the moment, I have an input file that validates to the destination JSON schema but has some missing fields and default values that the business rules permit as part of the transformation.

The source JSON is as follows:

    {
    "messageContext" : {
        "statement" : "mediaDelivered",
        "services" :[
            "mss"
        ],
        "domain":"my.xxx.yyy.create",
        "function":"FCG"

    },
    "header" : {
        "primaryEntityIDs":[
            "my:png::abcxxxx"
        ],
        "origin" : "my-png",

    },
    "data" :{
        "operation": "CREATE",
        "payload": {
            "s3": "https://wildwildwest.amazonaws.com/png/myimg.mov"
        }

    }
}

The JSON schema that the generated output JSON must match or validate to is as follows:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://schema.example.com/v1-0-0/schema.json",
  "definitions": {
    "header": {
      "type": "object",
      "title": "Message headers",
      "description": "Header properties for the message.",
      "required": [
        "serializedVersion",
        "correlationID",
        "projectID",
        "sourceMessageID",
        "eventTimestamp",
        "messageType",
        "primaryEntityIDs"
      ],
      "additionalProperties": false,
      "properties": {
        "serializedVersion": {
          "type": "string",
          "title": "serialized Version",
          "description": "The version of the schema to validate this message against, the SchemaVer convention.",
          "default": "1-0-0",
          "pattern": "^([1-9][0-9]*)-(0|[1-9][0-9]*)-(0|[1-9][0-9]*)$",
          "examples": [
            "1-0-0"
          ]
        },
        "correlationID": {
          "type": "string",
          "title": "Correlation ID",
          "description": "Use this field to pass on the ID from an upstream message. If you do not have an upstream message, please create a guid.",
          "examples": [
            "4fd5217e-a3d8-4ffc-8b8c-76ea9ba0ccdb"
          ]
        },
        "projectID": {
          "type": "string",
          "title": "Project ID",
          "description": "An ID for the current project, which is a grouping of messages that apply to a specific business context. For example: a commission, creating a new content version, migrating programmes to a new brand.",
          "examples": [
            "4fd5217e-a3d8-4ffc-8b8c-76ea9ba0ccdb"
          ]
        },
        "sourceMessageID": {
          "type": "string",
          "title": "Source Message ID",
          "description": "A unique ID for this message. Generated by the system that generates the message.",
          "examples": [
            "6b659d8f-bf4b-4dbe-8d31-5c6e9b8553b6"
          ]
        },
        "eventTimestamp": {
          "type": "string",
          "title": "Event Timestamp",
          "description": "The ISO 8601 datetime at which this message was created. Generated by the system that generates the message.",
          "examples": [
            "2015-07-09T10:45:01.555Z"
          ],
          "format": "date-time"
        },
        "expiryTime": {
          "type": "string",
          "title": "Expiry Time",
          "description": "The ISO 8601 datetime at which the message is no longer applicable. For a status message this would mean that the status message should be ignored after this time and the information in it considered no longer true. Timezone must be provided. Accuracy greater than 1s is not supported, greater accuracy will be truncated. If not provided time for hh, mm, or ss defaults to 00.",
          "examples":[
            "2015-07-09T10:45:01.555Z"
          ],
          "format": "date-time"
        },
        "origin": {
          "type": "string",
          "title": "origin",
          "description": "The specific system that generated this message. This field is for information only, and should not be coupled to. To understand the source of a message, please use messageContext/Function. This allows for looser coupling between systems and for multiple systems to provide the same function.",
          "examples": [
            "media-selector"
          ]
        },
        "messageType": {
          "type": "string",
          "title": "Message Type",
          "description": "The type of serialized message.",
          "enum": [
            "STATUS",
            "COMMAND",
            "ERROR"
          ],
          "examples": [
            "STATUS"
          ]
        },
        "inResponseTo": {
          "type": "string",
          "title": "In Response To",
          "description": "The message ID that this message is in response to.",
          "examples": [
            "6b659d8f-bf4b-4dbe-8d31-5c6e9b8553b6"
          ]
        },
        "primaryEntityIDs": {
          "type": "array",
          "title": "Primary Entity IDs",
          "description": "A list of equivalent IDs (preferably URIs) that systems use to describe the content this message is about. For example, this may contain a What's On UID, and a PEEPS version ID. Any further entity identifiers can be in the Payload section (for example the identifier of an availability document).",
          "items": {
            "type": "string",
            "title": "ID",
            "description": "IDs (preferably URIs) for my content identifiers.",
            "examples": [
              "urn:my:CMM1:uid:ABC123E/01",
              "urn:my:DST1:pid:p00123"
            ]
          }
        }
      }
    },
    "messageContext": {
      "type": "object",
      "title": "Message Context",
      "description": "The message context, combined with the Content-IDs section should contain all of the information needed to understand the purpose of this message.",
      "required": [
        "domain",
        "function",
        "statement"
      ],
      "additionalProperties": false,
      "properties": {
        "services": {
          "type": "array",
          "title": "Services",
          "description": "A list of services that this message is relevant to. For example, the editorial description of a piece of content may be approved only for on-platform services, such as mediaplayer.",
          "items": {
            "type": "string",
            "title": "A my service",
            "examples": [
              "mediaplayer",
              "my-plus",
              "twitter"
            ]
          }
        },
        "domain": {
          "type": "string",
          "title": "Domain",
          "description": "The top level address of the function sending the message. This follows a dot notation, starting with the company (e.g. my), moving down a domain hierarchy.",
          "examples": [
            "my.content.distribution"
          ]
        },
        "function": {
          "type": "string",
          "title": "Function",
          "description": "The business function sending the message. This is the generic function that the sending system is providing when sending this message. For example, Media Selector is providing the function of AvailabilityManagement, within the distribution domain. One system may have grown to provide more than one function, in which case the specific function that applies to this message should be used.",
          "examples": [
            "availabilityManagement"
          ]
        },
        "contentTypes": {
          "type": "array",
          "title": "Content Types",
          "description": "The content type that this message applies to. In the AvailabilityManagement example, this could separately message the availability of media, or document data separately, or together.",
          "items": {
            "type": "string",
            "enum": [
              "MEDIA",
              "DOCUMENT"
            ],
            "examples": [
              "MEDIA"
            ]
          }
        },
        "statement": {
            "type": "string",
            "title": "Statement",
            "description": "The string statement being made by the message which describes the event. This must be past-tense.",
            "pattern": "(^\\S+$)",
            "examples": [
                "receivedMedia",
                "linkedToPlanningItem",
                "renditionCreated"
            ]
        }
      }
    },
    "data": {
      "type": "object",
      "title": "Data",
      "description": "Dictionary describing the type of data being transferred and the transfer method. Includes either a reference to a data entity or an entity itself in a JSON document.",
      "additionalProperties": false,
      "properties": {
        "operation": {
          "type": "string",
          "title": "Operation",
          "description": "The CRUD operation that was performed.",
          "enum": [
            "CREATE",
            "READ",
            "UPDATE",
            "DELETE"
          ]
        },
        "entitySchema": {
          "type": "string",
          "title": "Entity Schema",
          "description": "The schema used for the object in the Payload.",
          "examples": [
            "http://url.my.dev/package.schema.json"
          ]
        },
        "entityType": {
          "type": "string",
          "title": "Entity Type",
          "description": "The type of entity being provided in the Payload.",
          "examples": [
            "package"
          ]
        }
      },
      "anyOf": [
        {
          "required": [
            "payload"
          ],
          "properties": {
            "payload": {
              "type": "object",
              "title": "Payload",
              "description": "The data entity being transferred. If both a reference and a payload are provided, then the objects in both must be identical.",
              "properties": {},
              "additionalProperties": true
            }
          }
        },
        {
          "required": [
            "reference"
          ],
          "properties": {
            "reference": {
              "type": "string",
              "title": "Reference",
              "description": "A reference to the data entity being transferred. If both a reference and a payload are provided, then the objects in both must be identical.",
              "examples": [
                "https://url.my.dev/3b0f45d6-9051-4939-974f-8cbb7774b1db"
              ]
            }
          }
        }
      ]
    }
  }
}

The rules are as follows:

enter image description here

I have been digging about and would like to know your thoughts on the best approach and Node libraries for solving this problem in NodeJS Javascript.


Solution

  • I found the Joi library to be a direct fit for my use case. I have tested and used it and it worked for me and gave me the need control to enforce the schema's constraints.

    https://www.npmjs.com/package/joi

    Another library the might be useful is https://www.npmjs.com/package/is-my-json-valid. However, I have not tested this library in depth.