3

I am dealing with some JSON data that I am retrieving from a database. If the result contains a single value, it creates a single object. If there are multiple values, it creates an array of objects.

My issue is that having to try and handle this becomes a problem when dealing with loops.

Example Data:

// Single result from DB
var obj = {
  "records": {
    "recordID": 1,
    "recordName": 'test'
  }
}

// Multiple results from DB
var obj = {
  "records": [{
    "recordID": 1,
    "recordName": 'test'
  }, {
    "recordID": 2,
    "recordName": 'test again'
  }]
}

I have a function that loops over all of the records for example and this becomes problematic when we only have one result because we are no longer looping over an array.


Due to some of my objects being pretty large, I am trying to come up with a function that I can initialize my object with when I get it back from the database before handling it.

This function would loop through all of the keys and check to see if the key exists in an array of "Does this need to be an array?" flags. If it finds a match, check to see if its a single object and if so, convert it to an array of that single object.

Here is some pseudo code of what I am trying to do:

// Input 
var obj = {
    "records": {
        "recordID": 1,
        "recordName": 'test'
    },
    "photos": {
        "photoID": 1,
        "photoName": 'test flower'
    },
    "leaveMeAlone": {
        "nopeID": 1,
        "nopeName": 'tester'
    }
}


function convertToArray(obj) {

    var keysToArray = ['records', 'photos'];

    // Loop over keys
    for (var k in obj) {

        // Properties
        if (obj.hasOwnProperty(k)) {

            // This key is in our array. 
            if (keysToArray.indexOf(k) > -1) {

                // If this is a single object, turn it into an array containing a single object
                if (!Array.isArray(obj[k])) {
                    // Turn this value into an array of the single object
                    /* Stuck Here */
                }
            }
        }
    }

    /* Return 
    
    var obj = {
        "records": [{
            "recordID": 1,
            "recordName": 'test'
        }],
        "photos": [{
            "photoID": 1,
            "photoName": 'test flower'
        }],
        "leaveMeAlone": {
            "nopeID": 1,
            "nopeName": 'tester'
        }
    }
    */

}

// run
convertToArray(obj);

10
  • 2
    Why the inconsistent data storing at the first place? If there's a single object, it shoud be stored into an array as well as multiple objects. Commented Sep 14, 2017 at 4:49
  • @Teemu - The database is MsSQL and when dealing with multiple nested records, we return the result as XML from the stored procedure (not on 2016 yet which allows for a JSON response). Our DB class that I am unable to change then converts the nested XML structure into the JSON data. In short, it's something out of my control so looking for a solution for the time being. Commented Sep 14, 2017 at 4:52
  • 1
    What library is it using to convert XML to JSON? There might be a way to always force a collection for certain elements Commented Sep 14, 2017 at 4:53
  • 1
    I would send some nasty greetings to the DB department, or at least a humble wish ... Commented Sep 14, 2017 at 4:55
  • 1
    @SBB if there's a chance for any particular record to be an array, it should always be an array. Dealing with data structure inconsistencies is just a nightmare Commented Sep 14, 2017 at 4:59

3 Answers 3

6

You can use below method which I created. It will check if object is an array or not. If not then it will put object inside an array and will return.

function convertToArray(obj) {
    if (obj.records instanceof Array) {
        return obj.records;
    } else {
        return [obj.records];
    }
}

JSFiddle https://jsfiddle.net/r4otdrq0/

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks but this doesn't quite answer what I need. I need to be able to pass an object of unknown structure and have it convert any single object into an array of that single object if I specified they key name. Please see @skirtle's answer - It is very close but I think it may be missing recursion and only uses a single level.
2

You could take an iterative and recursive approach by converting given properties to an array, if not already an array.

If all keys are unique, you could use an early return by deleting the properties of the array, or changing the the path from depth first to breadth first search.

function convert(object, keys) {
    Object.keys(object).forEach(function (k) {
        if (object[k] && typeof object[k] === 'object') {
            convert(object[k], keys);
        }
        if (keys.indexOf(k) !== -1 && !Array.isArray(object[k])) {
            object[k] = [object[k]];
        }
    });
}

var object = { records: { recordID: 1, recordName: 'test' }, photos: { photoID: 1, photoName: 'test flower' }, leaveMeAlone: { nopeID: 1, nopeName: 'tester' }, nested: { convert: { foo: 1, bar: 2 } } },
    keys = ['records', 'photos', 'convert'];

convert(object, keys);

console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }

7 Comments

Both this example and that of @skirtle look good. I am facing another issue at the moment with Angular appearing to not like calling its own method from within? Cannot read property 'convert' of undefined. Unrelated to OP, just trying to figure it out to test the solution :)
sorry, with angular, i can not help.
No problem, im sure this solution will work once I figure out how to get it implemented
I think I semi got this working.. How would I go about returning the object like you had mentioned?
what, exactly ?
|
0

Note that I've replaced the values inline so the object that is passed in is mutated. Other than that I'm hoping it's pretty self-explanatory.

// Input 
var obj = {
    "records": {
        "recordID": 1,
        "recordName": 'test'
    },
    "photos": {
        "photoID": 1,
        "photoName": 'test flower'
    },
    "leaveMeAlone": {
        "nopeID": 1,
        "nopeName": 'tester'
    }
}

function convertToArray(obj) {
    var keysToArray = ['records', 'photos'];

    keysToArray.forEach(function(key) {
        if (key in obj && !Array.isArray(obj[key])) {
            obj[key] = [obj[key]];
        }
    });

    return obj;
}

console.log(convertToArray(obj));

Based on your comment below it would seem that you need to perform this conversion on nested objects. Below is an example that will do that by calling itself recursively for arrays and objects. Note that it only checks the keys when deciding whether to convert to an array, it does not care about the path used to get there. This is fine so long as you are sure that the same key names will not be used in different places with different expectations:

function convertToArray(obj, keysToConvert) {
  if (Array.isArray(obj)) {
    obj.forEach(function(entry) {
      convertToArray(entry, keysToConvert);
    });
  }
  else if (obj && typeof obj === 'object') {
    Object.keys(obj).forEach(function(key) {
      if (keysToConvert.indexOf(key) !== -1 && !Array.isArray(obj[key])) {
        obj[key] = [obj[key]];
      }

      convertToArray(obj[key], keysToConvert);
    });
  }

  return obj;
}

var obj = {
  "data": {
    "VersionForTarget": "1",
    "rules": {
      "rule": {
        "RuleParentID": "84",
        "RuleVersionID": "2",
        "MappedValue": "1",
        "ProcessingOrder": "1",
        "MetaInsertUtc": "2017-03-03T17:54:34.643",
        "Value": "Recorded",
        "IsRuleRetired": "0",
        "UserImpactCount": "27130",
        "attributes": {
          "attribute": {
            "AttributeID": "2",
            "AttributeName": "Role",
            "attributeDetails": {
              "attributeDetail": [{
                  "RuleDetailID": "10964",
                  "AttributeID": "2",
                  "OperatorID": "3",
                  "AttributeValue": "172",
                  "RuleParentID": "84",
                  "Value": "Account Manager",
                  "IsValueRetired": "0",
                  "OperatorName": "In List",
                  "SqlOperator": "IN"
                },
                {
                  "RuleDetailID": "10966",
                  "AttributeID": "2",
                  "OperatorID": "3",
                  "AttributeValue": "686",
                  "RuleParentID": "84",
                  "Value": "Agent",
                  "IsValueRetired": "0",
                  "OperatorName": "In List",
                  "SqlOperator": "IN"
                }
              ]
            }
          }
        }
      }
    }
  }
}

console.log(convertToArray(obj, ['rule', 'attribute', 'attributeDetail']))

3 Comments

I am trying to implement this on some test data but seem to be having an issue. In my test, I would expect that rule should become an array? jsfiddle.net/zxp3we57/1
@SBB I have updated my answer to include a recursive example. Yuck.
Thanks, trying to implement now. Angular doesn't like it for some reason, Cannot read property 'convertToArray' of undefined. Will let you know shortly

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.