2

I have a problem like this Convert an HTML form field to a JSON object with inner objects but in to the other direction.

This is the JSON Object response from the server:

{
    company : "ACME, INC.",
    contact : {
        firstname : "Daffy", 
        lastname : "Duck"
    }
}

And this is the HTML form:

<form id="myform">
    Company: <input type="text" name="company" />
    First Name: <input type="text" name="contact.firstname" />
    Last Name: <input type="text" name="contact.lastname" />
</form>

And this is the (pseudo)code:

var aFormFields; 

for (var i = 0, iMax = aFormFields.length; i < iMax; i++) {

    var sFieldName = aFormFields[i].getAttribute('name');
    eval("sFieldValue = oResponse."+sFieldName);

}

Ok my solution works, but i looking for a good way to remove the evil eval from the code. And the solution should also work for form fields with any count of dots in the field name.

Community
  • 1
  • 1
netzzwerg
  • 386
  • 4
  • 9
  • Your instinct is correct: if you think you need to use `eval()`, then there's probably something new you need to learn about :-) Keep sniffing your code! – Pointy Nov 22 '10 at 15:12
  • I'm not quite sure: Do you want to fill the input fields from the AJAX server response, or do you want to generate the server response from the contents of the input fields? – RoToRa Nov 22 '10 at 15:13
  • On an unrelated note: the response from the server is not valid JSON. – JeremyP Nov 22 '10 at 15:24

5 Answers5

4

Instead of:

eval("sFieldValue = oResponse."+sFieldName); 

Use for single dotted fields:

sFieldValue = oResponse[sFieldName];

This will retrieve the value via its key.

Now if you need more than that you need to do the following:

  1. Split sFieldName on .
  2. Loop over that array and go down in oResponse till you reach the value that you desire

Code could look like this:

var node = oResponse, parts = sFieldName.split('.');
while(parts.length > 0) {
   node = node[parts.shift()];
}
// node will now have the desired value

Further information on "Member Operators":
https://developer.mozilla.org/en/JavaScript/Reference/Operators/Member_Operators

Ivo Wetzel
  • 46,459
  • 16
  • 98
  • 112
2

This works for a single property:

sFieldValue = oResponse[sFieldName]

But it won't work for nested data like contact.firstname. For that, split the name by dots, and use loop through each name:

var aFormFields; 

for (var i = 0, iMax = aFormFields.length; i < iMax; i++) {

    var aFieldNameParts = aFormFields[i].getAttribute('name').split(".");
    var oFieldValue = oResponse;
    for(var j=0; j<aFieldNameParts.length; j++) {
        oFieldValue = oFieldValue[aFieldNameParts[j]];
    }
    var sFieldValue = oFieldValue;
}

Note: if a property does not exist, an error will occur. You might want to check whether oFieldValue[ aFieldNameParts[j] ] exists or not.

Lekensteyn
  • 64,486
  • 22
  • 159
  • 192
0

please treat this as a combination of answer and question :)

i am currently trying to get my server to jsonify the data that i get sent from a form just like you...

in my case the form will in the end create a json object with multiple subobjects that can have subobjects which can have... as well.

the depth is up to the user so i should be able to support infinite recursion.

my "solution" so far just feels wrong, but it correctly does the job, the function getRequestBody gets fed a req.body object from expressjs, this is basically an object with the following mapping:

{
  "ridic-ulously-deep-subobject": "value",
  "ridic-ulously-deep-subobject2": "value",
  "ridic-ulously-deep2-subobject3": "value",
}

the following html is in use:

<form>
    <input name="ridic-ulously-long-class-string" value="my value" />
  </form>

and the javascript function (that should work genericly, feed it a req.body object like above and it will return a json object):

function getRequestBody(reqB){

  var reqBody = {};

  for(var keys in reqB) {

    var keyArr = keys.split('-');

    switch(keyArr.length){
      case 1:
        if(!reqBody[keyArr[0]]) reqBody[keyArr[0]] = {};
        reqBody[keyArr[0]] = reqB[keys];
      break;

      case 2:
        if(!reqBody[keyArr[0]]) reqBody[keyArr[0]] = {};
        if(!reqBody[keyArr[0]][keyArr[1]]) reqBody[keyArr[0]][keyArr[1]] = {};

        reqBody[keyArr[0]][keyArr[1]] = reqB[keys];
      break;

      case 3:
        if(!reqBody[keyArr[0]]) reqBody[keyArr[0]] = {};
        if(!reqBody[keyArr[0]][keyArr[1]]) reqBody[keyArr[0]][keyArr[1]] = {};
        if(!reqBody[keyArr[0]][keyArr[1]][keyArr[2]]) reqBody[keyArr[0]][keyArr[1]][keyArr[2]] = {};

        reqBody[keyArr[0]][keyArr[1]][keyArr[2]] = reqB[keys];
      break;

      case 4:

      // ...
      //and so on, always one line longer
  }

    return reqBody;
}

this just feels wrong and its only covering 5 levels of subobjects right now, it might happen that an application has enough functionality to reach seven or even ten levels though.

this should be a common problem, but my search effort turned up nothing within 10 minutes, which usually means that i am missing some keywords or that there is no viable solution [yet] (which i cant really imagine in this case).

is there someone out there who has imagination and logic sufficient enough to unspaghettify this or will i just have to expand this function with even more clutter to get me down to 10 possible sublevels?

i think that in the end it wont make a big difference performance wise, but i would really like NOT to create this awful behemoth :D

have fun

jascha

0

While it is possible, I wouldn't loop over the input fields, but over the JSON object:

function fillForm (form, data, prefix) {
  prefix = prefix ? prefix + "." : "";
  for (var x in data) {
    if (typeof data[x] === "string") {
      var input = form.elements[prefix + x];
      if (input)
        input.value = data[x];
    } else
      fillForm(form, data[x], prefix + x);
  }
}

fillForm(document.getElementById("myform"), oResponse);

(untested)

RoToRa
  • 37,635
  • 12
  • 69
  • 105
  • this is a good idea, but not in my case because i have more elements in the json object the fields in html form. so i prefer to loop over the form fields – netzzwerg Nov 22 '10 at 15:26
0

Assuming your naming scheme is consistent, you can convert the dot-notation into subscripts. You'd have to split the field name on the period and iterate or recurse over the tokens, converting each into a subscript. Of course this assumes that oResponse always contains a value for every field.

for (var i = 0; i < aFormFields.length; i++) {
    var sFieldName = aFormFields[i].getAttribute('name');
    var tokens = sFieldName.split('.');
    var cur = oResponse;

    for (var j = 0; j < tokens.length; j++) {
        cur = cur[tokens[j]];
    }

    sFieldValue = cur;
}
AndrewC
  • 394
  • 2
  • 4