marioizquierdo / jquery.serializeJSON

Serialize an HTML Form to a JavaScript Object, supporting nested attributes and arrays.
MIT License
1.71k stars 433 forks source link

Feature: compact null arrays #97

Closed ghost closed 5 years ago

ghost commented 5 years ago

This feature will compact arrays, removing null/undefined entries.

I needed this feature because I have a form that dynamically creates and deletes inputs. Form input names are automatically generated. Each index has several items under it. A user can go on a clicking rampage adding multiple forms then delete all but a few. This results in gaps, sometimes substantial. Rather than iterate through every input looking to see if it needs to be renumbered it seemed more wise to just remove them after the fact (less DOM searching, slightly faster in my environment).

Example:

<input name="foo[0][bar]">
<input name="foo[0][baz]">
<input name="foo[4][bar]">
<input name="foo[4][baz]">

would generate {foo: [[bar,baz],undefined,undefined,undefined,[bar,baz]]} with useIntKeysAsArrayIndex set to true.

skipFalsyValuesForFields is not as elegant of a solution as an object containing 1..N elements to cover the spread of inputs created would need to be created.

skipFalsyValuesForTypes and a custom type is not a viable solution as I would need foo[N] with the type not foo[N][bar].

marioizquierdo commented 5 years ago

I think you want to use useIntKeysAsArrayIndex but at the same time you want to ignore the actual index for an incremental index?

With the example input:

$(form).serializeJSON({useIntKeysAsArrayIndex: true});
//=> {foo: [{bar: "x", baz: "y"}, undefined, undefined, undefined, {bar: "a", baz: "b"}]}

$(form).serializeJSON({useIntKeysAsArrayIndex: true, compactArrays: true});
//=> {foo: [{bar: "x", baz: "y"}, {bar: "a", baz: "b"}]}

Unfortunately this seems too specific. I try to keep the plugin as simple as possible, avoiding solutions that are too specific for a given problem like this one.

However, it should be easy for you to work around that limitation. In cases like this, the best solution is to post-process the result. Instead of depending on a plugin option, you can modify the array after it was generated:

let vals = $(form).serializeJSON({useIntKeysAsArrayIndex: true});
vals.foo = vals.foo.filter(e => e); // compact array, remove undefined values
vals //=> {foo: [{bar: "x", baz: "y"}, {bar: "a", baz: "b"}]}

I hope this works for you. Thank you for providing a working pull request, that makes it easier to understand the problem you are trying to solve. To merge this code, we would need to agree on the need for adding this new option, add extensive unit tests and make sure the code is ES5 compliant (the plugin works with old versions of JavaScript too, I don't want to break that at this moment). For now, I hope you don't mind closing the PR.

ghost commented 5 years ago

I will close it, no problem. To address the remainder of the points raised I have included the following book er I mean post.

I only used useIntKeysAsArrayIndex because that forces list vs key-value entities ([] vs {}). The use of integers in my current dumpster fire set of requirements is to properly group sub-elements together in a way that would result in list entities. An alternative way of dealing with this would be to have a flag that just makes those items lists but preserving the grouping which seemed much more invasive to achieve. Another would be to build the final JSON out of successive calls to find the elements of interest (eg $.each($('formGroup'), function () {...})) and then slap it all together but that seemed less elegant, although it does deal effectively with the indexing problem.

I did not want undefined items in the resulting entities. It appears the API does not properly check if items, or their sub-elements are properly defined. I do not control the APIs I am currently interfacing with, so I have to deal with it, warts and all. I only offered this code because it may be helpful to others.

While your suggestion is basically the code I submitted (although mine will iterate the entire object to find arrays), and I could have that as a standalone function (which it was before making the fork). I thought it would be better to have it as part of the module as I was going to have to copy/pasta it into a couple different projects and thought if anyone else was going to use it they too would have to copy/pasta similarly.

The desired JSON is something akin to the following.

Issues:

event, event..occurrences, and somethingElse items can be dynamically added and removed. As a result index numbers can get out of order without great effort and care in managing them and possibly renumbering which has a greater impact on performance than my solution. I did not test the json below for validity so it probably has some typos (the real data is more complex but this is sufficient to illustrate the problem I faced).

The API is picky about when [] and when {} can be used. The API also dislikes undefined entities.

example form element <input name="event[0][occurrences][0][someOtherProperty]">

{
    "item": {
          "name":"some name",
          "title":"some title",
          "date":"some date"
    },
    "event": [
          "name":"event name",
          "type":"event type",
           "occurrences":[
                { "start":"some date","end":"some date","someOtherProperty":"bored yet?"},
                { "start":"some date","end":"some date","someOtherProperty":"I am"},
                { "start":"some date","end":"some date","someOtherProperty":":)"},
            ],
    ],
    "somethingElse":[
            {"field1":"data", "field2":"data", "field3":"data"},
            {"field1":"data", "field2":"data", "field3":"data"},
            {"field1":"data", "field2":"data", "field3":"data"}
    ]
}