skip to Main Content

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:

  • Initially it will get a common part from resulting array.
  • Then it conditionally merge other variations, condition based on value of common_conditional. In this example I need to merge to array.common keys from array.variation1 if array.common.common_conditional == "AB", variation2 if "CD", etc. It can be much more.

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?

2

Answers


  1. Chosen as BEST ANSWER

    The given above answer is good if I need to write a custom validation function, but if I need to combine everything into one schema, I've got this solution:

    function getMapping(data) {
      return {
        AB: data.variation1,
        CD: data.variation2,
        EF: data.variation3,
      };
    }
    
    function getSchemaFromParts(data) {
      const mapping = getMapping(data);
    
      const base_schema = data.common;
      let full_schema = base_schema;
    
      for (const [key, variation_schema] of Object.entries(mapping)) {
        full_schema = full_schema.when(
          joi.object({ common_conditional: key }).unknown(),
          { 
            then: base_schema.concat(variation_schema)
          }
        );
      }
      
      return full_schema;
    }
    

  2. 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
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search