javascriptnode.jsschemajoi

javascript: JOI merge fields conditionally


Haven't found any working solution for my case, so I'm creating a new one. I've got this architecture:

function getParts1() {
  return {
    common: joi.object({
      common_key: joi.number().integer().required(),
      common_conditional: joi.string().valid("AB", "CD", "EF").required(),
      common_value1: joi.string().max(144).required(),
    }),

    variation1: joi.object({
      field1: joi.number().allow(0).required(),
      field2: joi.string().max(255).allow("").required(),
    }),

    variation2: joi.object({
      another_field1: joi.number().allow(0).required(),
      another_field2: joi.string().max(255).allow("").required(),
    }),

    variation3: joi.object({
      super_another_field1: joi.number().allow(0).required(),
      super_another_field2: joi.string().max(255).allow("").required(),
    }),
  };
}

function getParts2() {}; //And much more functions like getParts1 with the same keys but different objects as values 

I need to create a function that can work with the output of this kind of functions in the next way:

Because of difference in joi.object(...) of each variation for each Parts, I can't find any solution for this problem.

For example, for the code above if such schema will be created:

const schema = getSchemaFromParts(getParts1());

const data1 = {
  common_key: 12,
  common_conditional: "AB",
  common_value1: "somevalue",
  field1: 14,
  field2: "Value for field2"
}

const data2 = {
  common_key: 16,
  common_conditional: "CD",
  common_value1: "somevalue2",
  another_field1: 18,
  another_field2: "Hello world"
}

const data3 = {
  common_key: 16,
  common_conditional: "EF",
  common_value1: "somevalue3",
  another_field1: 20,
  another_field2: "Broken data"
}

const res1 = schema.validate(data1);
const res2 = schema.validate(data2);
const res3 = schema.validate(data3);
console.log(res1.error)
console.log(res2.error)
console.log(res3.error)

It should pass the res1 and res2, but res3 should throw an error of ValidationError: "another_field1" is not allowed { …(2) }. Is there any solutions for this case, or maybe it's better to create a different architecture for the Parts function?


Solution

  • The problem sounds quite straightforward, let me know if this solution fits your needs. The script below implements directly what you have stated in requirements. And is generic and reusable so you only will have to add new variations and new keys into CONDITIONS map without making any changes to getSchemaBasedOnConditional method.

    const Joi = require("joi");
    
    // Define reusable condition mappings
    const CONDITIONS = {
      AB: "variation1",
      CD: "variation2",
      EF: "variation3",
    };
    
    function getParts() {
      return {
        common: Joi.object({
          common_key: Joi.number().integer().required(),
          common_conditional: Joi.string()
            .valid(...Object.keys(CONDITIONS)) // convert object keys (conditions) into an array of strings
            .required(),
          common_value1: Joi.string().max(144).required(),
        }),
    
        variation1: Joi.object({
          field1: Joi.number().allow(0).required(),
          field2: Joi.string().max(255).allow("").required(),
        }),
    
        variation2: Joi.object({
          another_field1: Joi.number().allow(0).required(),
          another_field2: Joi.string().max(255).allow("").required(),
        }),
    
        variation3: Joi.object({
          super_another_field1: Joi.number().allow(0).required(),
          super_another_field2: Joi.string().max(255).allow("").required(),
        }),
      };
    }
    
    // Dynamically create schema based on common_conditional
    function getSchemaBasedOnConditional(data) {
      const parts = getParts();
    
      // Start with the common schema
      let schema = parts.common;
    
      // Retrieve the appropriate variation schema based on CONDITIONS mapping
      // 1. Check common conditional ["AB", "CD", "EF"]
      // 2. Get the variation key to lookup a variation object
      // 3. concat parts[variationKey]
      const variationKey = CONDITIONS[data.common_conditional];
      if (variationKey && parts[variationKey]) {
        schema = schema.concat(parts[variationKey]);
      }
    
      return schema;
    }
    
    // Validate data based on dynamically generated schema
    function validateData(data) {
      const schema = getSchemaBasedOnConditional(data);
      return schema.validate(data);
    }
    
    // Test cases
    const data1 = {
      common_key: 12,
      common_conditional: "AB",
      common_value1: "somevalue",
      field1: 14,
      field2: "Value for field2",
    };
    
    const data2 = {
      common_key: 16,
      common_conditional: "CD",
      common_value1: "somevalue2",
      another_field1: 18,
      another_field2: "Hello world",
    };
    
    const data3 = {
      common_key: 16,
      common_conditional: "EF",
      common_value1: "somevalue3",
      another_field1: 20, // This field does not exist in `variation3`
      another_field2: "Broken data",
    };
    
    console.log(
      "res1:",
      validateData(data1).error
        ? validateData(data1).error.details[0].message
        : "Valid"
    ); // valid
    console.log(
      "res2:",
      validateData(data2).error
        ? validateData(data2).error.details[0].message
        : "Valid"
    ); // valid
    console.log(
      "res3:",
      validateData(data3).error
        ? validateData(data3).error.details[0].message
        : "Valid"
    ); // error