2

Anyone know of a good way to convert a JSON object to nested form fields.

For example: consider a JSON object:

{'a':{'b':{'c':'1200'}}}, 'z':'foo', 'bar':{'baz':'1', 'id':2}}

I should get:

{'a[b][c]':'1200', 'z':'foo', 'bar[baz]':'1', 'bar[id]':2};

Any ideas?

I am currently using jquery and it feels like something like this already exists, if not I can simply roll my own with a crazy algorithm, but I'd rather use something with a proven track record.

ReNiSh AR
  • 2,782
  • 2
  • 30
  • 42
user86847
  • 149
  • 2
  • 4
  • 1
    What's a nested form field? Are these fieldset elements? I don't quite understand what algorithm you're even implying to get from the first object to the second. – Anonymous May 01 '09 at 21:42
  • Um, what, exactly, is this mess? You may need to provide some clarification because this doesn't make a whole lot of sense without more context... – Jason Bunting May 01 '09 at 22:31
  • Would the fact that you already can refer to elements as obj['a']['b'['c'] change your form-field traversing algorithm to a little better? – Thevs May 02 '09 at 09:06
  • I have the same problem, I am using FormData to pass some file attachment but using the form.append method is very tedious if I have a big nested form. – Alex C Mar 06 '15 at 18:53

3 Answers3

3

So, I have no clue why you want to do what you say you want to do, and I hope you will fill us all in, but this code should be close enough for you to be able to tweak it (this is based on some code of mine that I use to find differences in JavaScript object graphs):

function doStrangeThing(obj) {
   var propertyChanges = [];
    var objectGraphPath = [];
    (function(obj, refObj) {
        if ( obj.constructor == Object || (obj.constructor != Number &&
             obj.constructor != String && obj.constructor != Date && obj.constructor != Boolean &&
             obj.constructor != RegExp && obj.constructor != Function)) {
            for (var property in obj) {
                objectGraphPath.push((objectGraphPath.length > 0) ? "[" + property + "]" : property);
                if (obj[property].constructor != Function) {
                    if (!refObj[property]) refObj[property] = {};
                    arguments.callee(obj[property], refObj[property]);
                }
                objectGraphPath.pop();
            }
        } else if (obj.constructor != Function) {
            if (obj != refObj) {
                propertyChanges.push("\"" + objectGraphPath.join("") + "\":\"" + obj.toString() + "\"");
            }
        }
    })(obj, {});
    return "{" + propertyChanges.join(",") + "}";
}

Here is what I did to test it:

doStrangeThing({'a':{'b':{'c':'1200'}}, 'z':'foo', 'bar':{'baz':'1', 'id':2}});

Which results in this value:

{"a[b][c]":"1200","z":"foo","bar[baz]":"1","bar[id]":"2"}

Hope that is useful to you in some way...

Community
  • 1
  • 1
Jason Bunting
  • 58,249
  • 14
  • 102
  • 93
  • once this reaches the serverside, in php it becomes a nested hash map. – user86847 May 02 '09 at 04:01
  • Well, if it works for you, please mark it as the answer - I don't know anything about PHP or the need for this, but obviously you have your reasons. :) Again, it could probably use a little tweaking to make it do things EXACTLY as you would like it to, but this was relatively quick for me to test and play with, since I had written this code quite a while ago... – Jason Bunting May 02 '09 at 04:08
  • When I say "tweak," by the way, I mean to get the value types working correctly - I see that you wanted the value of "bar[id]" to be the literal number 2, not a string ("2") - you could tweak the code to test to see if something is a number and if so, not use quotations; etc. – Jason Bunting May 02 '09 at 04:09
  • 1
    Jason Bunting: +1 for doing it for free. I know how that's ironic. :) – Tomalak May 02 '09 at 07:20
  • @unknown (yahoo): Since you seem to have jQuery at you disposal, I feel obliged to tell you that you may be better off sending JSON to the server (http://code.google.com/p/jquery-json/) and have PHP evaluate the JSON (http://de.php.net/manual/en/function.json-decode.php). I would say that this has the type of track record you were looking for. – Tomalak May 02 '09 at 07:24
  • @Tomalak: Yeah, I have to assume the OP knows what he/she is doing, because the results he requested look like useless @*$!! to me. I feel an inkling that there is a better way to do what he is intended on doing with this than to do it this way, but I thought it would be "fun" to see if I could get it to work, as nightmarish as it looks. :) – Jason Bunting May 02 '09 at 16:12
  • 1
    @JasonBunting this is very relevant if you re stuck using FormData like me :). – Alex C Mar 06 '15 at 18:57
1
obj = {'a':{'b':{'c':'1200'}}}, 'z':'foo', 'bar':{'baz':'1', 'id':2}}

is internally equivalent to

{['a']['b']['c']:'1200', ['z']:'foo', ['bar']['baz']:'1', ['bar']['id']:2}

Please note that this is not JSON object anymore.

You already can refer first object properties in this way:

var z = obj['a']['b']['c']   // 1200

Is it enough for your needs? Do you really want to convert property names to variables?

Thevs
  • 3,189
  • 2
  • 20
  • 32
  • I had to laugh when I read this answer because I think I sense the same feeling I had when I read the question - i.e. WTF is this guy going to do with this mess?! It was an interesting exercise nonetheless, but I don't know what "unknown" is doing with this stuff in PHP. Weird stuff. – Jason Bunting May 02 '09 at 16:15
  • Actually, I got what he wanted. Quite weird, but it have some sence. However, there are plenty of simpler algorithmic ways to achieve the same goal... And there would be a big mess with variable names and namespaces, if it should have been implemented for common usage. – Thevs May 02 '09 at 18:53
0

I would actually recommend a function that takes a nested JSON object and turns it into a HTTP POST string. jQuery will accept that for all of its arguments that require HTTP parameters.

I have written and used the function below in several production apps (use at your own risk):

$.httpSerialize = function(items, parentName) {
    var serializedItems = [], serialize = arguments.callee, encodeItem =     function(key, value) {
        if (value === null || typeof value == 'undefined') return value;
        if (value.constructor == Array) {return serialize(value, key);}
        return (value.constructor == Object)
            ? serialize(value, key)
            : (value === true || value === false)
                ? key+"="+new Number(value)
                : key+"="+encodeURIComponent(value);
    };

    if (items.constructor == Array) {
        parentName = parentName || 'item';
        for (var i = 0; i < items.length; i++) {
            var key = parentName+'['+i+']', value = items[i];
            serializedItems.push(encodeItem(key, value));
        }
    } else {
        parentName = parentName || '';
        for (var key in items) {
            var value = items[key];
            if (parentName) {
                serializedItems.push(encodeItem(parentName+'['+encodeURIComponent(key)+']', value));
            } else {
                serializedItems.push(encodeItem(encodeURIComponent(key), value));
            }
        }
    }
    return serializedItems.join("&");
};
Felix Geisendörfer
  • 2,902
  • 5
  • 27
  • 36