1

//lets say the form is like below

 <form id="myform">
      <input name="foo" value="parent"/>
      <input name="foo.cat.bar" value="child1"/>
      <input name="foo.cat.biz" value="child2"/>
      <input name="foo.cat.biz.dog.bar" value="child3"/>
  </form>

// I want the output to be like this in JSON format based on the input name attr //(this is just an example, want generic answer and the best way to do it)

{
    "foo": "parent",
    "foo.cat": {
        "bar": "child1",
        "biz": "child2",
        "foo.cat.biz.dog": {
            "bar": "child3"
        }
    }
}
Mohit Tomar
  • 23
  • 1
  • 1
  • 3
  • What have you tried yourself so far? – Andrew Li Jul 05 '17 at 03:00
  • if you want the object when you submit the form, you can create a nested object from "name" fields with split by dot for nesting the data. – binariedMe Jul 05 '17 at 03:02
  • alternatively you should use a framework to achieve this – binariedMe Jul 05 '17 at 03:02
  • Your desired output seems a bit inconsistent. Why is the last nested object attached to property `"foo.cat.biz.dog"` and not `"biz.dog"`? – nnnnnn Jul 05 '17 at 03:08
  • I am thinking of doing it using multiple objects and then based on the condition (if the name is same till second last element), assign the values to new object. Finally, push objects into each other. However, this is very hard code approach and doesn't sound like good programming. I am not able to think of something else right now. – Mohit Tomar Jul 05 '17 at 03:08
  • Not only does the requested output not make sense neither does the requested input. If you `name=foo` then your object foo is going to be a string and strings can't have child objects like bar. So if you wanted nested objects in foo you would need to drop foo as a named element. – Alexander Higgins Jul 05 '17 at 03:29

3 Answers3

6

You can use jQuery and use the serializeArray() method like this var jsonData = $('form').serializeArray();:

It doesn't match your sample object but it does what your question asked.

Once you have the fields in an Array you can manipulate your object however you want. I can't quite follow the logic of how you could ever arrive with that object from the form name attributes so I can't even attempt to map it.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form id="myform">
      <input name="foo" value="parent"/>
      <input name="foo.cat.bar" value="child1"/>
      <input name="foo.cat.biz" value="child2"/>
      <input name="foo.cat.biz.dog.bar" value="child3"/>
  </form>
  <input id='btnSerialize' value='Serialize' type='button' onclick='serializeForm()' />
  <div id='result'></div>
  <script>
 
  function serializeForm()
  {
    var jsonData = $('form').serializeArray();
    var jsonString = JSON.stringify(jsonData);
    $('#result').html(jsonString);
  }

  </script>

If you change your form names so you can have a valid logical structure than in Plain javascript:

/*
{
  "foo": {
    "bar": "parent",
    "biz": "parent",
    "cat": {
      "bar": "child2achild2a,child2b",
      "biz": {
        "dog": {
          "bar": "child3"
        }
      }
    }
  }
}
*/
<form id="myform">
  <input name="foo.bar" value="parent" />
  <input name="foo.biz" value="parent" />
  <input name="foo.cat.bar" value="child2a" />
  <input name="foo.cat.bar" value="child2b" />
  <input name="foo.cat.biz.dog.bar" value="child3" />
</form>
<script>
  function serialize() {
    var elements = document.querySelectorAll('#myform input');
    var data = {};
    for (var i = 0; i < elements.length; i++) {
      var el = elements[i];
      var val = el.value;
      if (!val) val = "";
      var fullName = el.getAttribute("name");
      if (!fullName) continue;
      var fullNameParts = fullName.split('.');
      var prefix = '';
      var stack = data;
      for (var k = 0; k < fullNameParts.length - 1; k++) {
        prefix = fullNameParts[k];
        if (!stack[prefix]) {
          stack[prefix] = {};
        }
        stack = stack[prefix];
      }
      prefix = fullNameParts[fullNameParts.length - 1];
      if (stack[prefix]) {

        var newVal = stack[prefix] + ',' + val;
        stack[prefix] += newVal;
      } else {
        stack[prefix] = val;
      }
    }
    console.log(data);

  }
</script>
<input type="button" value="go" onclick="serialize()" />
<p><br /></p>
<p><br /></p>
Alexander Higgins
  • 6,765
  • 1
  • 23
  • 41
  • 3
    *"It doesn't match your sample but it does what your question asked"* - The sample forms part of the question, and also the first few words of the question ask "How to create a *nested* object", and not only is the "jQuery" tag not present the title explicitly says "vanilla JavaScript". – nnnnnn Jul 05 '17 at 03:10
  • Understood, but as I stated I can't even follow the logic of how you would arrive with that object from the form input fields. Once you have an array you can map it however you would like. – Alexander Higgins Jul 05 '17 at 03:11
  • Thanks for the solution. However, i am looking for a plain JavaScript approach instead of a library. – Mohit Tomar Jul 05 '17 at 03:11
  • It was asked to me in an interview. I gave an example. The essence is to create a nested object of the form input value based on input name. What's the hurry to downvote a question. – Mohit Tomar Jul 05 '17 at 03:16
  • Vanilla implementation added, but your form element names are invalid for what you asking. The point of the interview question was likely to gain insight to your thought process and how you would go about getting the solution. – Alexander Higgins Jul 05 '17 at 04:41
1

Here you go. I've included an explanation in the comments...

function getFormData() {
    /* return nested array combined
       into groups of two. See question @ 
       https://stackoverflow.com/a/31352555/4746328 */
    function groupIntoPairs(arr) {
        var temp = arr.slice();
        var out = [];

        while (temp.length) {
            out.push(temp.splice(0,2));
        }

        return out;
    }

    /* create a storage object */
    var data = {},
    /* get 'input' elements as an array */
    inputs = [].slice.call(document.getElementById('myform').querySelectorAll('input')),
    /* additional variables */
    name, hold, splits, L, dKey;

    /* loop through input elements */
    inputs.forEach(function(n) {
        name = n.name;

        /* for holding key strings */
        hold = '';

        /* split the 'name' at '.'
           and group into pairs */ 
        splits = groupIntoPairs( name.split('.') );

        /* index of last item in 'splits' */
        L = splits.length - 1;

        /* if 'splits' has only one
           item add the name-value pair
           to 'data' straight away */
        if (L === 0) {
            data[name] = n.value;
        } else {
            /* loop 'splits' to create keys */
            splits.forEach(function(x, i) {
                /* combine key strings until
                   last item in 'splits' */
                if (i !== L) hold += '.' + x.join('.');
            });

            /* define the key */
            dKey = hold.slice(1);

            /* create 'data[dKey]' Object if
               it doesn't exist or use it
               again if it does */
            data[dKey] = data[dKey] || {};

            /* add last item in 'splits' as 
               key for 'data[dKey]' and 
               assign current n.value */
            data[dKey][splits[L][0]] = n.value;                
        }
    });
    /* return 'data' object */
    return data;
}

console.log('data:', JSON.stringify(getFormData(), null, 4));
/* => data: {
    "foo": "parent",
    "foo.cat": {
        "bar": "child1",
        "biz": "child2"
    },
    "foo.cat.biz.dog": {
        "bar": "child3"
    }
}
*/

Hope that helped.

Brian Peacock
  • 1,801
  • 16
  • 24
  • 1
    This doesn't work if inputs share the same name (very common with checkbox lists and radio buttons). One value will get lost. – Alexander Higgins Jul 05 '17 at 04:43
  • 2
    @AlexanderHiggins Indeed, but it works with the specific format offered in the question and the example returns the requested JSON output. ;) – Brian Peacock Jul 05 '17 at 05:07
0

You can post complex type object like below;

$("#btnSubmit").click(() => {
    postForm("/home/myaction", complexTypeInstance);
});

function postForm(path, instance) {
    var form = document.createElement('form');
    form.setAttribute('method', 'post');
    form.setAttribute('action', path);
    generateFormInputs(form, instance, "");
    document.body.appendChild(form);
    form.submit();
}

function generateFormInputs(form, instance, prefix) {
    for (var key in instance) {
        if (instance.hasOwnProperty(key)) {
            
            if (instance[key] instanceof Object) {
                generateFormInputs(form, instance[key], key+".");
            }
            else {
                var hiddenField = document.createElement('input');
                hiddenField.setAttribute('type', 'hidden');
                hiddenField.setAttribute('name', prefix + key);
                hiddenField.setAttribute('value', instance[key]);
                form.appendChild(hiddenField);
            }
        }
    }
}