Tuesday 6 December 2011

Convert HTML form to JSON and POST using jQuery

Reinventing the wheel is a pet hate of mine; I could list quite a few actually, but that's not the point of this post.  Sometimes it takes me so long to look for a wheel that I think has or should have already been invented that it would have been easier just to invent the wheel myself.

Recently I was playing around with jquery, pulling fields from a form and posting them as a JSON object to a set of services.  Nothing fancy, nothing difficult there.  However, when the data model started to mature beyond the most basic structure I soon realised I need an easy, repeatable way to pull structured/nested data from a a form and convert it into a JSON object prior to posting.

To create the following structure:


You will need to create the following references:


This is a complete working example that will parse the form, create the JSON obbject, POST it to a service and display the returned object in a table.



The form above will produce the following JSON.


32 comments:

  1. Thanks html5 audio player - if that is your real name (-:

    I've been on leave for a while and have a few things I need to catch up on right now, but will post something soon.

    Thanks for taking the time to post a comment.

    ReplyDelete
  2. Thanks but it doesn't work posting to Jersey. I saw through Firebug that nothing was being sent as the "Content-Type". Strange.

    ReplyDelete
  3. Anonymous. I'm afraid you're going to have to give me more information to go on. Are you getting any errors? Have you wrapped your js code in a try/catch?

    FYI: This is in production and working fine from Firefox and Chrome to Jersey (1.12). I'm sure my customers would let me know if it weren't working (-;

    BTW: I ask my customers to only use Firefox or Chrome for full support of REST verbs.

    ReplyDelete
  4. Hello,

    Thanx for the exmaple made. I tried it by parts and the JSON creation works well. I would need to create a JSON from a form containing: input text, input checkboxes and input radio. I will try to extend your exmaple toward solving my problem and the question is: did you already do it (this way I will just start from your code otherwise I will do it).

    Kind regards,

    panda

    ReplyDelete
    Replies
    1. Hi panda,

      I've not had the requirement to include checkboxes or radio buttons. The only thing you'll need to remember is that if you have multiple sets of each, you'll need to group them on the 'name' attribute.

      Hope this helps.

      Delete
  5. I'm trying really hard to get your example to work and I keep getting an error alert: "Status = 0 Message = error"

    The only thing I have changed is the url and the form fields. Any ideas?

    Could the fact that the url I need to post the json to is structured like this: https://api.litmos.com/v1.svc/users?apikey=1A2B-3C4D-5E6F&source=xyz

    My form looks like this:
    form id="form"
    input id="FirstName" type="text" name="FirstName" placeholder="First Name..."
    input id="LastName" type="text" name="LastName" placeholder="Last Name..."
    input id="Email" type="text" name="Email" placeholder="Email..."
    input id="UserName" type="text" name="UserName" placeholder="Confirm Email..."
    input id="DisableMessages" type="hidden" name="DisableMessages" value="false"
    input id="SkipFirstLogin" type="hidden" name="SkipFirstLogin" value="true"
    input id="input" type="submit" name="submit" value="Post It"
    /form

    Thanks!

    ReplyDelete
    Replies
    1. Hi Dave,

      Have you tried adding any debugging or writing logs to console?

      Is the alert originating from this line:
      alert('Error! Status = ' + xhr.status + ' Message = ' + error);
      If so, it would be worth setting up a proxy to see what the server response is, if the status code of the reponse is not being set, then you'll get an error.

      Is your service expecting a form with the stated content-type and data in json format?

      Delete
    2. Hi, thanks for your reply. I have not tried debugging much or console logs because I'm not very familiar with js and don't know how to go about it.

      Yes, I believe the alert is originating from alert('Error! Status = ' + xhr.status + ' Message = ' + error);

      The service accepts xml or json and expects whichever format is declared in the content-type. It also returns data in the same format it was received.

      The service API documentation is very vague and only states that a POST should be made to the api url (as shown above) and that the data can be sent in either xml or json format. Other than that and a list of the 'fields' accepted by the api that's all I have to go on.

      Thanks for your help.

      Delete
    3. You could either add:
      console.log($("#form").formToJSON());
      or
      alert($("#form").formToJSON());
      Just above the line line that reads:
      var send = $("#form").formToJSON();

      This will display the json that will be sent to the server. You'll be able to see whether the json is valid from this.

      If the error occurs before you get to that line, the I'd suggest you add alert or console.log earlier to see where the issue is occurring.
      If you were more of a js developer I'm pretty sure you'd be able to use the debugger to step through the script.

      Let me know how it turns out for you.

      Delete
    4. I used alert($("#form").formToJSON()); and it displayed the correct data in json format {"FirstName":"Dave",...}

      So, the data just isn't making it to the service?

      Delete
    5. So, to me it looks like you may need to check the end point; where the json is being posted to. Is this the format the service is expecting.
      Secondly, I'd set up a proxy. Use something like burp http://www.portswigger.net/burp/download.html Download the free edition, set up the proxy to, say, port 8080 and point your browser at localhost and port 8080. The proxy will, well proxy your requests. You'll see the request and response from the server/service. If the request looks ok and the response doesn't, then it's probably time to look at what the service is doing and returning.

      Let me know. Happy to help debug with you.

      Delete
  6. When I do a test POST using Dev HTTP Client (Chrome Extension) I can set the body to the following and everything works fine. The user is created and the correct json data is returned.

    {"UserName":"bruce@waynetech.com","FirstName":"Bruce","LastName":"Wayne","Email":"bruce@waynetech.com","DisableMessages":false,"Active":true,"SkipFirstLogin":false}

    Can the order in which the service receives the json data elements determine pass/fail?

    I did notice that in the json I used for the test POST, the boolean values are not wrapped in double-quotes. Can that make a difference? In your example all values are wrapped in double-quotes when the object is prepared.

    I really appreciate your time, thank you.

    ReplyDelete
    Replies
    1. So it looks like your service is expecting the boolean values without quotes. Have you tried wrapping your boolean values in quotes when using the chrome extension? If it fails using the extension with quotes around the boolean values, then you could change the service to expect the values with quotes.

      Note: this example was not designed to used typed values, so mixing String and Boolean in the same array is completely untested.

      If you cannot or do not want to change the service to expect only String:String name value pairs, the you could probably parse the json that is produced from the formToJSON method and strip out the quotes around the values that you want to be represented as a Boolean type.

      Depending on how the service parses the json it receives may have an influence the order; though it's difficult to tell without seeing the code. Normally the ordering shouldn't really matter.

      Let me know how the test of adding quotes around the boolean values turns out for you.

      Delete
    2. Thanks for your reply. Unfortunately the service is not internal so I have no control.

      I did do a test using quotes for the boolean values as well as changing the order and both were successful.

      I'm at the limit of my knowledge on this subject and their support hasn't given me any help except to direct me to their vague documentation -- which I've already memorized at this point.

      Any other suggestions?

      Delete
    3. OK.

      So from an earlier reply you gave (6 November 2012 20:43), the json being sent is the same as that from the chrome extension. Can you 100% confirm this?

      When using the chrome extension, and the call to the API is working, what is the http status code returned in the header?

      Delete
    4. 100%. To confirm this I took the json generated by your code, which I got using this -> alert($("#form").formToJSON());, and I pasted it into the chrome extension. Worked perfectly.

      I received a status of "201 Created". And it returned the user data back to me in json - as it should.

      Delete
    5. OK.

      In which case, try this.

      statusCode: {
      201: function() {
      alert("User created");
      }
      },

      Try replacing this code fragment:

      error: function(xhr, error) {
      alert('Error! Status = ' + xhr.status + ' Message = ' + error);
      },

      Delete
    6. Well, I replaced the fragment as you suggested and nothing happened - no alert. I assume I should have seen the alert if a 201 response was received. They have a few more responses listed in their documentation (400,403, 404) and I tried those as well with no alert.

      So, I'm guessing that I'm not even making a connection to the service? Seems like the only thing it could be is the URL string. Maybe there's a special character that should be escaped or something?

      Delete
    7. I'm getting this:

      Status Code:
      HTTP/1.1 401 '1A2B-3C4D-5E6F' is an invalid apikey or source is missing

      Delete
    8. Did you get that using your form? That's not the real API key so that's a correct response.

      Delete
  7. Hi,

    As with all the others thank you for spending the time to do this, I am trying, unsuccessfully to combine your code with the JSON2XML code found here (http://goessner.net/download/prj/jsonxml/json2xml.js), to convert the JSON to XML before sending it to the server. Do you have any pointers? Do you know of an easier way? I am not an expert with JS at all.
    Many thanks

    ReplyDelete
    Replies
    1. You can try this:

      function parseJSONArray ( jsonArrRoot, jsonArrObj, attrList ) {
      var result = "";
      if(jsonArrRoot.length == 0) {
      result+=startTag(jsonArrRoot, jsonArrObj, attrList, true);
      }
      else {
      for(var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) {
      result+=startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false);
      result+=parseJSONObject(jsonArrRoot[arIdx]);
      result+=endTag(jsonArrRoot[arIdx],jsonArrObj);
      }
      }
      return result;
      }

      Delete
    2. and this:

      function parseJSONAttributes ( jsonObj ) {
      var attrList = [];
      if(jsonObj instanceof Object ) {
      for( var ait in jsonObj ) {
      if(ait.toString().indexOf("__")== -1 && ait.toString().indexOf("_")==0) {
      attrList.push(ait);
      }
      }
      }
      return attrList;
      }
      function endTag(jsonObj,elementName) {
      return "";
      }
      function endsWith(str, suffix) {
      return str.indexOf(suffix, str.length - suffix.length) !== -1;
      }
      function parseJSONTextAttrs ( jsonTxtObj ) {
      var result ="";

      if(jsonTxtObj.__cdata!=null) {
      result+="";
      }

      if(jsonTxtObj.__text!=null) {
      result+=jsonTxtObj.__text;
      }
      return result
      }
      function startTag(jsonObj, element, attrList, closed) {
      var resultStr = "<"+ (jsonObj.__prefix!=null? (jsonObj.__prefix+":"):"") + element;
      if(attrList!=null) {
      for(var aidx = 0; aidx < attrList.length; aidx++) {
      var attrName = attrList[aidx];
      var attrVal = jsonObj[attrName];
      resultStr+=" "+attrName.substr(1)+"='"+attrVal+"'";
      }
      }
      if(!closed)
      resultStr+=">";
      else
      resultStr+="/>";
      return resultStr;
      }
      function jsonXmlSpecialElem ( jsonObj, jsonObjField ) {
      if(endsWith(jsonObjField.toString(),("_asArray"))
      || jsonObjField.toString().indexOf("_")==0
      || (jsonObj[jsonObjField] instanceof Function) )
      return true;
      else
      return false;
      }
      function jsonXmlElemCount ( jsonObj ) {
      var elementsCnt = 0;
      if(jsonObj instanceof Object ) {
      for( var it in jsonObj ) {
      if(jsonXmlSpecialElem ( jsonObj, it) ) {
      continue;
      }
      elementsCnt++;
      }
      }
      return elementsCnt;
      }

      function parseJSONTextObject ( jsonTxtObj ) {
      var result ="";

      if( jsonTxtObj instanceof Object ) {
      result+=parseJSONTextAttrs ( jsonTxtObj )
      }
      else {
      if(jsonTxtObj!=null) {
      result+=jsonTxtObj;
      }
      }
      return result;
      }

      function parseJSONObject ( jsonObj ) {
      var result = "";
      var elementsCnt = jsonXmlElemCount ( jsonObj );
      if(elementsCnt > 0) {

      for( var it in jsonObj ) {

      if(jsonXmlSpecialElem ( jsonObj, it) )
      continue;

      var subObj = jsonObj[it];
      var attrList = parseJSONAttributes( subObj )

      if(subObj!=null && subObj instanceof Object) {
      if(subObj instanceof Array) {
      result+=parseJSONArray( subObj, it, attrList )
      }
      else {
      result+=startTag(subObj, it, attrList, false);
      result+=parseJSONObject(subObj);
      result+=endTag(subObj,it);
      }
      }
      else {
      result+=startTag(subObj, it, attrList, false);
      result+=parseJSONTextObject(subObj);
      result+=endTag(subObj,it);
      }
      }
      }
      result+=parseJSONTextObject(jsonObj);
      return result;
      }


      And where submit click is captured, you can try this:

      var xml;
      var send;
      try {
      send = $("#form").formToJSON();
      console.log(send);
      xml = parseJSONObject(JSON.parse(send));
      console.log(xml);
      }
      catch(e) {
      alert("poop: " + e.message);
      }

      Delete
    3. Had to split this into multiple responses as a reply takes a maximum of 4096.

      I've tried this and it works and should be generic enough to handle any JSON structure.

      Let me know how you get on.

      Delete
  8. Can you please tell that if i can save that json object like - "output.json" in a file on some place and then read from there ?

    ReplyDelete
  9. If I wanted to use a dropdown list, how to get those data into the JSON structure when I click submit?

    ReplyDelete
  10. For parsing, how would I be able to get:

    Notice that meh is now meh:X where x is some number.

    "meh:0": {
    "feh": "what ever the user types into the form field",
    "bleh": "what ever the user types into the form field"
    }

    ReplyDelete
  11. This has been a big help. The problem I'm having is that some of my data is numeric (with decimal), but this script stringifies everything, which breaks my calculation. I'm trying to figure out how to incorporate parsefloat(), with no success.

    ReplyDelete


  12. The company offers you a good day best شركة كشف تسربات بالرياض , we see a lot of people who are present in Riyadh complain of water leaks in Riyadh , many of the customers carry the search for شركة كشف تسربات بالرياض via the Internet , we see that a lot OF companies in those pages do not have the benef of
    because those companies do not have any expertise in this area , because that service you need to
    1. The best crew of trainees the best skills of workers in those difficult service , the workers considered the most important and the best things that lead the company's success and also the success of the tasks they need customers
    You should visit شركة كشف تسربات الغاز بالرياض

    ReplyDelete
  13. For parsing, how would I be able to get:

    Notice that meh is now meh:X where x is some number.

    "meh:0": {
    "feh": "what ever the user types into the form field",
    "bleh": "what ever the user types into the form field"
    }

    ReplyDelete

  14. Hi there everyone, I have come across almost all the web development sites. However, your website is far better than other in form of content, tools and easiness of using website. Since I am a developer, I am always looking forward to make people aware of web development tools. I can help you people out by introducing you to range of json tools. Here is the link jsononline

    ReplyDelete