简体   繁体   中英

Avoiding nested each loops when handling nested JSON data

I have a JSON object:

{
   "custom_sql_rule":[
      {
         "custom_sql":[
            "Should start with a Select",
            "Should not contain ;",
            "Some other error"
         ],
         "something_else":[
            "Error",
            "Should not contain ;",
            "Some other error"
         ]
      }
   ],
   "someother_rule":[
      {
         "sdfsdf":[
            "Should start with a Select",
            "Should not contain ;",
            "Some other error"
         ],
         "sdfsdf":[
            "Error",
            "Should not contain ;",
            "Some other error"
         ]
      }
   ]
}

I need to append each string error to a div. I quickly whipped this up:

var errorMessages = function(errors, errorsContainer) {
  $.each(errors, function(index, value) {
    $.each(value, function(i, v) {
      $.each(v, function(ia, va) {
        $.each(va, function(iab, vab) {
          errorsContainer.append($("<div></div>").addClass(index).text(vab));
        })
      })
    })
  });
};

It's terrible. Is there a nicer way of handling JSON formed like this? NOTE, I cannot really use keynames.

Here's a solution that will iterate over a tree of objects and arrays, identifying each string.

function eachLeafString(dataObject, callbackMethod) {
    if(typeof dataObject == 'string') {
        callbackMethod(dataObject);
    }
    else if (Object.prototype.toString.call( dataObject ) === '[object Array]') {
        $.each(dataObject, function(index, elem) { eachLeafString(elem, callbackMethod); });
    }
    else {
        for(var propertyName in dataObject) {
            eachLeafString(dataObject[propertyName], callbackMethod);
        }
    }
}

For an example of this working: http://jsfiddle.net/e6XN7/3/

If you want to use the index of the first-level nodes, you could use it as follows:

  $.each(errors, function(index, value) {
    eachLeafString(value, function(vab) {
         errorsContainer.append($("<div></div>").addClass(index).text(vab));
    });
  });

JSON is "just" javascript object literals. You can skip the jQuery call, and just reference the objects and arrays directly after you parse it.

obj.custom_sql_rule[0].something_else[0];

of course, your JSON is a bit oddly-formed. If at all possible, don't arbitrarily mix objects {} and arrays [] . Use the latter when you have lists of identical objects or primitive data, and the former when you have named properties. And once you have a valid javascript object, you can just iterate through its properties.

for(var sqlProp in jsonObj) {
  if(jsonObj[sqlProp].error) {
    errorsContainer.append("<div>" + jsonObj[sqlProp].error + "</div>");
  }
}

Try a recursion function like this :

function errorMessages(errors,errorsContainer,errorclass)
{

  if ( typeof errors == "string")
    errorsContainer.append($("<div></div>").addClass(errorclass).text(errors));
  else
  {
    $.each(errors, function(classname, value)
          {

           errorMessages(value,errorsContainer,(typeof errorclass == "undefined") ? classname : errorclass );                           
      });
   }
};

Check it on jsfiddle

When you call your function, dont pass the third argument, he will be null so your class will be get from your JSON. If you pass a class in thir param, it will overide the one in your JSON. You choose :)

But you can modify and optimise it for sure...

Instead of modifying your code that runs through the JSON, you can modify the structure of the JSON itself and make it "wider/flatter" instead of "deeper".

//errors

[
  {
    "error" : "custom_sql_error",
    "subcategory" : "custom_sql",
    "messages" : [
      "Should start with a Select",
      "Should not contain ;",
      "Some other error"
    ]
  },
  {
    "error" : "custom_sql_error",
    "subcategory" : "something_else",
    "messages" : [
      "Error",
      "Should not contain ;",
      "Some other error"
    ]
  },
  {
    "error" : "someother_rule",
    "subcategory" : "sdfsdf",
    "messages" : [
      "Should start with a Select",
      "Should not contain ;",
      "Some other error"
    ]
  },
  {
    "error" : "someother_rule",
    "subcategory" : "sdfsdf",
    "messages" : [
      "Error",
      "Should not contain ;",
      "Some other error"
    ]
  }
]

That way, you loop through like this

$.each(errors, function(index, value) {
  $.each(value.messages, function (index, message){
    $("<div/>").addClass(index).text(message).appendTo(errorsContainer);
  });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM