skip to Main Content

I’m trying to write a recursive function that receives an object with two branches of nested objects, and at the output returns one object with two properties that contain an array with all the specified values of the input object.

const obj = 
  { main: 
    { value: { value: 'main value'}
    , anotherValue : 'some another value'
    }
  , offhand: 
    { value: 
      { value: 'offhand value'
      , anotherValue: 'again recursion'
      }
    }
  }

function devider(object) {
  if (typeof object == 'string') {
    return object;
  }
  if (typeof object == 'object') {
    let data = {
      fromMainHand: [],
      fromOffHand: []
    };

    for (const key in object) {
      const value = object[key];
      if (typeof value == 'string') {
        data.fromMainHand.push(devider(value));
        data.fromOffHand.push(devider(value));
      }
    }
    return data;
  }
}

console.log(devider(obj)); // {fromMainHand: Array(0), fromOffHand: Array(0)}

Must be:

{ fromMainHand : ['main value', 'some another value']
, fromOffHand  : ['offhand value', 'again recursion']
}

3

Answers


  1. Several issues:

    • Your sample input has an object literal with twice the same property. As a consequence, your input does not have the value "offhand value".> (You corrected this later in the question).

    • Your devider function returns an object with fromMainHand and fromOffHand properties, and so when you make a recursive call, with .push(devider(value)), you’re adding an object to the array, not a string value or multiple string values. The object with the two properties should only be created once.

    • If the input would have numbers, booleans, or any other primitive value that is not a string, those values are ignored.

    I would split this problem into two parts:

    1. Create a function that retrieves all nested primitive values from an object.
    2. Create another function that takes an object and translates all its values with the above described function.

    The first function is a good candidate for a generator function. So then it looks like this:

    function* allValues(obj) {
        if (Object(obj) !== obj) { // It's a primitive value
            return yield obj;
        }
        for (const child of Object.values(obj)) {
            yield* allValues(child);
        }
    }
    
    function convert(obj) {
        return Object.fromEntries(Object.entries(obj).map(([key, val]) =>
            [key, [...allValues(val)]]
        ));
    }
    
    // Example run
    const obj = {
        main: {
            value: {
                value: 'main value'
            },
            anotherValue: 'some another value'
        },
        offhand: {
            value: {
                value: 'offhand value',
                anotherValue: 'again recursion'
            }
        }
    };
    
    const result = convert(obj);
    console.log(result);

    In comments you write there should be one recursive function, which can be done in many ways. For instance, you could return the flattened arrays for each property of the given object, returning that new object, and after a recursive you could flatten that object completely to an array:

    function convert(obj) {
        if (Object(obj) !== obj) { // It's a primitive value
            return [obj];
        }
        return Object.fromEntries(Object.entries(obj).map(([key, val]) =>
            [key, Object.values(convert(val)).flat()]
        ));
    }
    
    // Example run
    const obj = {
        main: {
            value: {
                value: 'main value'
            },
            anotherValue: 'some another value'
        },
        offhand: {
            value: {
                value: 'offhand value',
                anotherValue: 'again recursion'
            }
        }
    };
    
    const result = convert(obj);
    console.log(result);
    Login or Signup to reply.
  2. When creating recursive code, I find a functional appoach is often the simplist way of approaching the task. This is split into the flattenValues() function for the nested objects and a mapObject() function to do the first layer.

    const flattenValues = (obj) =>
      typeof obj === 'object'
        ? Object
            .values(obj)
            .map(i => flattenValues(i))
            .flat()
        : obj
      
    const mapObject = (func) => (obj) => Object
      .entries(obj)
      .map(([key, value]) => [key, func(value)])
      .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) 
      
    const devider = mapObject(flattenValues)
    
    console.log(devider(obj))
    

    A simpler solution can be achived via using the functional library Ramda. I find this code a bit cleaner and unlike native JS, the map() function in Ramda supports objects.

    You can have a play with this version the Ramda codepen.

    import { flatten, identity, ifElse, is, map, pipe, values } from 'ramda'
    
    const flattenValues = ifElse(
      is(Object),
      pipe(
        values,
        map(i => flattenValues(i)),
        flatten
      ),
      identity
    )
    
    const devider = map(flattenValues)
    
    console.log(devider(obj))
    

    In functional programming identity is just (x) => x, we have to pass an else function to ifElse() and this just tells it to do nothing.

    In both cases I had to tidy up the input data a little, as one object had duplicate keys.

    const obj = {
      main: { 
        value: { 
          value: 'main value'
        },
        anotherValue : 'some another value'
      },
      offhand: { 
        value: {
          value: 'offhand value',
          anotherValue: 'again recursion'
        }
      }
    }
    
    Login or Signup to reply.
  3. Without additional context about the structure of the object and the specific keys involved, there’s a risk of misinterpreting the data and pushing values to the wrong arrays. More information about the expected structure and keys would help ensure accurate categorization.

    const obj = {
        main: {
            value: { value: 'main value' },
            anotherValue: 'some another value'
        },
        offhand: {
            value: {
                value: 'offhand value',
                anotherValue: 'again recursion'
            }
        }
    };
    
    const data = {
        fromMainHand: [],
        fromOffHand: []
    };
    
    function devider(obj, currentKey = null) {
        for (const key in obj) {
            const value = obj[key];
            const currentKey_ = currentKey ? `${currentKey}.${key}` : key;
    
            if (typeof value === 'object') {
                devider(value, currentKey_);
            } else {
                if (currentKey_.startsWith('main')) {
                    data.fromMainHand.push(value);
                } else if (currentKey_.startsWith('offhand')) {
                    data.fromOffHand.push(value);
                }
            }
        }
    }
    
    devider(obj);
    console.log(data);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search