0

I need to transform a string

1. "user.member.staffAddress"
2. "user.something"

to object:

1. { user: { member: { staffAddress: {} } } }
2. { user: { something: {} } }

Does anyone has an elegant way how to do it? It should always be an object in object. The last property should be an empty one.

3 Answers 3

1

I wrote a utility that I think you will find helpful for this: https://github.com/forms-js/forms-js/blob/master/source/utils/flatten.ts

Here's the relevant bits. It's written in TypeScript but if you remove the :type annotations, it's valid JavaScript.

/**
 * Writes a value to the location specified by a flattened key and creates nested structure along the way as needed.
 *
 * <p>For example, writing "baz" to the key 'foo.bar' would result in an object <code>{foo: {bar: "baz"}}</code>.
 * Writing 3 to the key 'foo[0].bar' would result in an object <code>{foo: [{bar: 3}]}</code>.
 */
function write(value:any, flattenedKey:string, object:any):void {
  var currentKey:any;
  var keyIndexStart = 0;

  for (var charIndex = 0, length = flattenedKey.length; charIndex < length; charIndex++) {
    var character = flattenedKey.charAt(charIndex);

    switch(character) {
      case '[':
        currentKey = flattenedKey.substring(keyIndexStart, charIndex);

        createPropertyIfMissing_(currentKey, object, Array);
        break;
      case ']':
        currentKey = flattenedKey.substring(keyIndexStart, charIndex);
        currentKey = parseInt(currentKey); // Convert index from string to int

        // Special case where we're targeting this object in the array
        if (charIndex === length - 1) {
          object[currentKey] = value;
        } else {

          // If this is the first time we're accessing this Array key we may need to initialize it.
          if (!object[currentKey] && charIndex < length - 1) {
            switch(flattenedKey.charAt(charIndex + 1)) {
              case '[':
                object[currentKey] = [];
                break;
              case '.':
                object[currentKey] = {};
                break;
            }
          }

          object = object[currentKey];
        }
        break;
      case '.':
        currentKey = flattenedKey.substring(keyIndexStart, charIndex);

        // Don't do anything with empty keys that follow Array indices (e.g. anArray[0].aProp)
        if (currentKey) {
          createPropertyIfMissing_(currentKey, object, Object);
        }
        break;
      default:
        continue; // Continue to iterate...
        break;
    }

    keyIndexStart = charIndex + 1;

    if (currentKey) {
      object = object[currentKey];
    }
  }

  if (keyIndexStart < flattenedKey.length) {
    currentKey = flattenedKey.substring(keyIndexStart, flattenedKey.length);

    object[currentKey] = value;
  }
}

/**
 * Helper method for initializing a missing property.
 *
 * @throws Error if unrecognized property specified
 * @throws Error if property already exists of an incorrect type
 */
function createPropertyIfMissing_(key:string, object:any, propertyType:any):void {
  switch(propertyType) {
    case Array:
      if (!object.hasOwnProperty(key)) {
        object[key] = [];
      } else if (!(object[key] instanceof Array)) {
        throw Error('Property already exists but is not an Array');
      }
      break;
    case Object:
      if (!object.hasOwnProperty(key)) {
        object[key] = {};
      } else if (typeof object[key] !== 'object') {
        throw Error('Property already exists but is not an Object');
      }
      break;
    default:
      throw Error('Unsupported property type');
      break;
  }
}

To be fair, you could also consider a project written specifically for doing this - rather than mine, in which it's only a small portion - which is to say, https://github.com/hughsk/flat

Sign up to request clarification or add additional context in comments.

1 Comment

Very nice solution. A complex but +1 for writing a value to the last key in the object. I did not specifically ask for it in my question but I see this as a nice option in my project.
0

Iterate and add the properties etc ...

function stringToObject(str) {
    var obj = {}, arr = str.split('.');
    
    (function it(o) {
        var key = arr.shift();
        o[key] = {};
        if (arr.length) it(o[key]);
    }(obj));
    
    return obj;
}

var obj = stringToObject("user.member.staffAddress");

document.body.innerHTML = JSON.stringify(obj, null, 4);

1 Comment

This looks like a more compact solution, but it doesn't handle edge-cases (like arrays). Not sure if author was concerned about them though.
0

A string conversion approach:

var str = 'user.member.staffAddress'
var str_arr = str.split('.')
var obj = JSON.parse(
    '{ "' + str_arr.join('": { "') + '": {}' 
    + Array(str_arr.length+1).join(' }')
)
console.log(obj)
// { "user": { "member": { "staffAddress": {} } } }
  1. Split the string.
  2. Join elements with ": { ".
  3. Wrap new string with { " and ": {} followed by length number of closing curly braces.
  4. Parse final string as JSON object.

JSFiddle Demo

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.