4

A similar question is addressed here: Convert form data to JavaScript object with jQuery with the following solution:

$.fn.serializeObject = function()
{
    var o = {};
    var a = this.serializeArray();
    $.each(a, function() {
        if (o[this.name] !== undefined) {
            if (!o[this.name].push) {
                o[this.name] = [o[this.name]];
            }
            o[this.name].push(this.value || '');
        } else {
            o[this.name] = this.value || '';
        }
    });
    return o;
};

However that doesn't work when you want to have JSON with nested objects. For instance, for this form:

Employee name:      <input type="text" id="employee" name="name"/>
Department name:    <input type="text" id="department" name="department.name"/>
Department chief1:  <input type="text" id="chief1" name="department.chief[0]"/>
Department chief2:  <input type="text" id="chief2" name="department.chief[1]"/>

I am getting: {"name":"John","department.name":"IT" , "department.chief[0]":"Mike" "department.chief[1]":"Lucas"}

but what I do need is {"name":"John","department" : {"name":"IT"}, "chief" : [ "Mike", "Lucas" }

Is there any adaptation of serializeObject that does it this way?

To provide some background: I need this because I am submitting an AJAX call to a Spring MVC controller and Jackson expects that format to create the nested objects.

Community
  • 1
  • 1
codependent
  • 23,193
  • 31
  • 166
  • 308

2 Answers2

4

This is my modified version of serializeObject supporting both nested objects and arrays:

$.fn.serializeObject = function() {
    var arrayData, objectData;
    arrayData = this.serializeArray();
    objectData = {};

    $.each(arrayData, function() {
        this.value = !this.value ? '' : this.value;
        processObject(objectData, this.name, this.value);
    });

    return objectData;
};

function processObject(obj, key, value){
    if(key.indexOf('.') != -1) {
        var attrs = key.split('.');
        var tx = obj;
        for (var i = 0; i < attrs.length - 1; i++) {
            var isArray = attrs[i].indexOf('[') != -1;
            var isNestedArray = isArray && (i != attrs.length-1);
            var nestedArrayIndex = null;
            if(isArray){
                nestedArrayIndex = attrs[i].substring(attrs[i].indexOf('[') +1 , attrs[i].indexOf(']'));
                attrs[i] = attrs[i].substring(0, attrs[i].indexOf('['));
                if (tx[attrs[i]] == undefined){ 
                    tx[attrs[i]] = [];
                }
                tx = tx[attrs[i]];
                if(isNestedArray){
                    if(tx[nestedArrayIndex] == undefined){
                        tx[nestedArrayIndex] = {};
                    }
                    tx = tx[nestedArrayIndex];
                }

            }else{
                if (tx[attrs[i]] == undefined){ 
                    tx[attrs[i]] = {};
                }
                tx = tx[attrs[i]];
            }               
        }
        processObject(tx, attrs[attrs.length - 1], value);
    }else{
        var finalArrayIndex = null;
        if(key.indexOf('[') != -1){
            finalArrayIndex = key.substring(key.indexOf('[') +1 , key.indexOf(']'));
            key = key.substring(0, key.indexOf('['));               
        }
        if(finalArrayIndex == null){
            obj[key] = value;
        }else{
            if(obj[key] == undefined){
                obj[key] = [];
            }
            obj[key][finalArrayIndex] = value;
        }           
    }       
}
codependent
  • 23,193
  • 31
  • 166
  • 308
2

unflatten from the (npm) "flat" module should do the trick:

var data = {"name":"John","department.name":"IT"}
var nested = flat.unflatten(data)

Live example https://tonicdev.com/lipp/unflatten

Just replace return o with return flat.unlatten(o) in your case.

This is the unflatten method for the browser extracted and un-noded from repo(https://github.com/hughsk/flat/blob/master/index.js):

function unflatten(target, opts) {
  opts = opts || {}

  var delimiter = opts.delimiter || '.'
  var overwrite = opts.overwrite || false
  var result = {}

 
  // safely ensure that the key is
  // an integer.
  function getkey(key) {
    var parsedKey = Number(key)

    return (
      isNaN(parsedKey) ||
      key.indexOf('.') !== -1
    ) ? key
      : parsedKey
  }

  Object.keys(target).forEach(function(key) {
    var split = key.split(delimiter)
    var key1 = getkey(split.shift())
    var key2 = getkey(split[0])
    var recipient = result

    while (key2 !== undefined) {
      var type = Object.prototype.toString.call(recipient[key1])
      var isobject = (
        type === "[object Object]" ||
        type === "[object Array]"
      )

      // do not write over falsey, non-undefined values if overwrite is false
      if (!overwrite && !isobject && typeof recipient[key1] !== 'undefined') {
        return
      }

      if ((overwrite && !isobject) || (!overwrite && recipient[key1] == null)) {
        recipient[key1] = (
          typeof key2 === 'number' &&
          !opts.object ? [] : {}
        )
      }

      recipient = recipient[key1]
      if (split.length > 0) {
        key1 = getkey(split.shift())
        key2 = getkey(split[0])
      }
    }

    // unflatten again for 'messy objects'
    recipient[key1] = unflatten(target[key], opts)
  })

  return result
}
lipp
  • 5,586
  • 1
  • 21
  • 32