Home > Enterprise >  JS using dot, string notation to map nested and assign errors
JS using dot, string notation to map nested and assign errors

Time:03-13

Ok, this is an odd one that I just can't seem to get right.

I have a large, complex object to send to a backend. After various attempts, I tried using Joi to validate the schema, and I like it, but passing the errors back to the inputs is a nightmare

The body is subdivided into 5 sections, with each subsection containing between 10-30 fields, some of which are string[], interface[], number[], or general nested interfaces.

I tried writing my own custom validation and the complexity grew outta control.

(I know some of you are thinking "your schema is too complex" and you're right, but its not something I can change right now. Clients blah blah.)

The problem: Joi.validate(myBody) gives me a bunch of errors in the following format:

[ // <- error.details
  {
    context: {},
    message: "X is not allowed to be empty",
    path:["path","to","property"], // <- this is the key
    type: ""
  }
]

How can I map error.details to create a new validation object that I can then use for the form items themselves.

For example:

path = ["path","to","property"] // -> map over to create

let newObj = {
  path:{
    to: {
       property: ""
    }
}
}

I hope this make sense.

I want to take an array of vallidation errors, and turn them into a validation object that matches the initial object

CodePudding user response:

The simplest approach IMO would be to use reduce to reverse create the object from the array

["path","to","property"].reduceRight((prev, current) => {
  const obj = {};
  
  obj[current] = prev
  
  return obj;
}, "");

This will create the object as described in the original question. You need to use reduceRight rather than reduce so that you create the leaf node first otherwise you have having to try and traverse the graph each time you add a new node and you have to handle setting the leaf node to be a string rather than an object.


Extrapolating out what you are trying to achieve I'm assuming a couple of things:

  • You want to return a single object rather than an array of objects.
  • The leaf is likely to be the message.
  • There will only be a single error message for each path (because it makes life easier).

We can expand upon the above with the deep merge solution from here to create an object to return. The code for that would look like:

const errors = [ // <- error.details
  {
    context: {},
    message: "X is not allowed to be empty",
    path:["path","to","property"], // <- this is the key
    type: ""
  },
   {
    context: {},
    message: "X has to be greater than 0",
    path:["path","to","another", "property"], // <- this is the key
    type: ""
  }
]

function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

function mergeDeep(target, ...sources) {
  if (!sources.length) return target;
  const source = sources.shift();

  if (isObject(target) && isObject(source)) {
    for (const key in source) {
      if (isObject(source[key])) {
        if (!target[key]) Object.assign(target, { [key]: {} });
        mergeDeep(target[key], source[key]);
      } else {
        Object.assign(target, { [key]: source[key] });
      }
    }
  }

  return mergeDeep(target, ...sources);
}

errors.map((e) => e.path.reduceRight((prev, current) => {
  const obj = {};
  
  obj[current] = prev
  
  return obj;
}, e.message)).reduce((previous, current) => mergeDeep(previous, current), {})

The output from running errors through it would be:

{
  path: {
    to: {
      property: 'X is not allowed to be empty',
      another: { property: 'X has to be greater than 0' }
    }
  }
}

CodePudding user response:

Problem statement being used

I want to take an array of vallidation errors, and turn them into a validation object that matches the initial object

Given: an array of strings (each of which is a prop)

Expected result: an object structured based on the array

Code Snippet

// given an array of props 
// starting at root-level & ending at the leaf
const pList = ['root', 'lev1', 'lev2', 'propName'];

// method to transform array into an object
const transformToObj = arr => (
  arr
  .reverse()
  .reduce(
    (fin, itm) => ({ [itm]: fin ? {...fin} : '' }),
    false
  )
);

// test with given array
console.log(transformToObj(pList));

// test with user-input array (comma-separated)
console.log(transformToObj([...(
  (prompt('Enter comma-separated path like a,b,c,d'))
  .split(',')
  .map(x => x.trim())
)]));

Explanation

  • first reverse the array (so the first item is the inner-most prop)
  • use .reduce to iterate
  • at each level, add the item as the outer-prop and the value as the existing object
  • if this is the inner-most prop, simply add an empty string as value
  • Related