Search code examples
javascriptjqueryarraysjsonjavascript-objects

HTML form with hierarchical fields to JSON object


For example. We have this form:

<form id="to-object">
<input name="data[zero_key]" value="It`s too simple" />
<input name="data[first_key][value]" value="It`s simple too" />
<input name="data[second_key][]" value="Push me" />
<input name="data[second_key][]" value="Push me please" />
<input name="data[next_key][0][type]" value="I don`t wanna be object with key 0. " />
<input name="data[next_key][0][number]" value="I`m array!!!" />
</form>

So the question is: How to serialize this form correctly and get this result:

    {
  "zero_key": "It`s too simple",
  "first_key": {
    "value": "It`s simple too"
  },
  "second_key": [
    "Push me",
    "Push me please",
  ],
  "next_key": [
    {
      "type": "I don`t wanna be object with key 0.",
      "number": "I`m array!!!"
    },

  ],

}

What do I have:

(function($) {
    $.fn.convertFormDataToObject = function(){

        var extractFieldNames = function(fieldName, expression, keepFirstElement)
        {

            expression = expression || /([^\]\[]+)/g;
            keepFirstElement = keepFirstElement || false;

            var elements = [];

            while((searchResult = expression.exec(fieldName)))
            {
                    elements.push(searchResult[0]);

            }

            if (!keepFirstElement && elements.length > 0) elements.shift();

            return elements;
        }

        var attachProperties = function(target, properties, value)
        {

            var currentTarget = target;
            var propertiesNum = properties.length;
            var lastIndex = propertiesNum - 1;
            for (var i = 0; i < propertiesNum; ++i)

            {
                currentProperty = properties[i];

                if(currentProperty == ""){
                    var intKey = Math.floor(Math.random() * (99 - 1)) + 1;
                    currentProperty = intKey.toString();
                } else if(!isNaN(currentProperty)) {
                    currentProperty = [parseInt(currentProperty)];
                } else {
                    currentProperty = properties[i];
                }

                if (currentTarget[currentProperty] === undefined)
                {
                    currentTarget[currentProperty] = (i === lastIndex) ? value : {};                    }
                                    currentTarget = currentTarget[currentProperty];
            }
        }

        var convertFormDataToObject = function(form) {
                var currentField = null;
            var currentProperties = null;
                // result of this function
            var data = {};
            // get array of fields that exist in this form
            var fields = form.serializeArray();
            for (var i = 0; i < fields.length; ++i)
            { currentField = fields[i];
                // extract field names
                currentProperties = extractFieldNames(currentField.name);
                // add new fields to our data object
                attachProperties(data, currentProperties, currentField.value);
            }
            return data;
        }
        var form = $(this);
        var data = convertFormDataToObject(form);
        return data;
    };
})(jQuery);

As you can see in this sample I'm using random int generator to fix issue with empty space between []. That's a problem but not crucial. Crucial thing is that I'm not able to fix issue with arrays generating. This script adds all keys from name attribute as key of object and it gives wrong object. Instead of array with objects it gives object with keys "0","1" etc. For example "0":{},"1":{} instead of [{},{}].

If you know how to fix it with JS I'll very appreciate your help!


Solution

  • You can reduce the complexity of your code by quite a lot, try something like this:

    const finalObj = [...document.querySelector('form').children].reduce((objSoFar, child) => {
      const value = child.value;
      let allKeys = child.name.slice(4).split('][');
      allKeys[0] = allKeys[0].slice(1);
      const lastKeyIndex = allKeys.length - 1;
      allKeys[lastKeyIndex] = allKeys[lastKeyIndex].slice(0, allKeys[lastKeyIndex].length - 1);
      // now: eg "data[next_key][0][number]" has allKeys ["next_key", "0", "number"]
      
      let refObj = objSoFar;
      while (allKeys.length > 1){
        if (!refObj[allKeys[0]]) {
          if (allKeys[1] === '' || /^\d+$/.test(allKeys[1])) refObj[allKeys[0]] = [];
          else refObj[allKeys[0]] = {};
        }
        refObj = refObj[allKeys[0]];
        allKeys = allKeys.slice(1);
      }
      
      const lastKey = allKeys[0];
      if (lastKey === '') refObj.push(value);
      else refObj[lastKey] = value;
      return objSoFar;
    }, {});
    console.log(finalObj);
    
    const desiredSerializedResult = '{"zero_key":"It`s too simple","first_key":{"value":"It`s simple too"},"second_key":["Push me","Push me please"],"next_key":[{"type":"I don`t wanna be object with key 0.","number":"I`m array!!!"}]}';
    if (JSON.stringify(finalObj) === desiredSerializedResult) console.log('Matches desiredSerializedResult');
    <form id="to-object">
      <input name="data[zero_key]" value="It`s too simple" />
      <input name="data[first_key][value]" value="It`s simple too" />
      <input name="data[second_key][]" value="Push me" />
      <input name="data[second_key][]" value="Push me please" />
      <input name="data[next_key][0][type]" value="I don`t wanna be object with key 0." />
      <input name="data[next_key][0][number]" value="I`m array!!!" />
    </form>

    (But this really seems like an XY problem - there's almost certainly a better way to set up your application than something that requires logic like this)