TonyW
TonyW

Reputation: 18875

JavaScript: Return result from recursion

I wrote the following function to traverse a JSON-structured data to locate the value of the provided targetField and the paths pathArr, but I am struggling with returning result from the recursion, because the value for the targetField may not exist.

This function uses a global variable named result for returning the targetField's value, but this can get carried over to the next call of findValue(), which may not find the value but shows the last call's result. So I am wondering what's the best way of solving this problem without using the global variable result.

If the call of findValue() does not find the targetField's value, just return null as the result.

The pathArr is something like ["first_level", "second_level", "third_level"];

var result = null;
function findValue(jsonData, pathArr, targetField) {

if (pathArr.length == 0) {
    result = targetField in jsonData ? jsonData[targetField] : result;
    return result;
} else {

    var curNode = pathArr.shift();

    if (curNode in jsonData) {
        jsonData = jsonData[curNode];
    }

    if (Array.isArray(jsonData)) {
        jsonData.forEach(function(thisData) {
            findValue(thisData, pathArr, targetField);
        });
    } else {
        findValue(jsonData, pathArr, targetField);
    }
}   

return result;
}

Edit: Thanks to everyone who replied.

Using unobf's improved code, I used this testData to do a testing, but it seems only returning the last match:

var testData = {
"num_found" : 3,
"category" : "social",

"groups": [ 
{"group" : {
    "source" : [{"id" : "testID1", "num": 10, "field": "sociaology", "sub-subject" : "socialeconomy"}, 
                {"id" : "testID2", "num": 20, "field": "mathematics", "sub-subject": ""}, 
                {"id" : "testID3", "num": 7, "field": "biology", "sub-subject" : ""}
                ],
    "identifier" : "shelf-01-E-XW1"
}},
{"group" : {
    "source" : [{"id" : "testID4", "num": 50, "field": "sociaology2", "sub-subject" : ""}, 
                {"id" : "testID5", "num": 44, "field": "mathematics2", "sub-subject": ""}, 
                {"id" : "testID6", "num": 75, "field": "biology2", "sub-subject" : "european studies2"}
                ],
    "identifier" : "shelf-02-W-EW3"
}},

{"group" : {
    "source" : [{"id" : "testID7", "num": 59, "field": "sociaology3", "sub-subject" : "socialeconomy3"}, 
                {"id" : "testID8", "num": 47, "field": "mathematics3", "sub-subject": ""}, 
                {"id" : "testID9", "num": 76, "field": "biology3", "sub-subject" : "european studies3"}
                ],
    "identifier" : "shelf-03-W-GW5"
}}
]
};

and I found I had to change this line of code

return {value : result};

to

return result;

Test:

for (var i = 0; i < testData.groups.length; i++) {
    var value = findValue(testData.groups[i], ["group", "source"], "sub-subject");
    console.log("found value: " + value);
}

Upvotes: 0

Views: 122

Answers (2)

unobf
unobf

Reputation: 7244

Here is the solution with some test data

function findValue(jsonData, pathArr, targetField) {
    var result = null;

    if (pathArr.length == 0 && jsonData.hasOwnProperty(targetField)) {
        result = targetField in jsonData ? jsonData[targetField] : result;
        return { value : result};
    } else {
        jsonData = jsonData[pathArr.shift()]
        if (Array.isArray(jsonData)) {
            jsonData.forEach(function(thisData) {
                result = findValue(thisData, pathArr, targetField);
            });
        } else if (typeof jsonData !== 'undefined') {
            return findValue(jsonData, pathArr, targetField);
        }
    }   
    return result;
}

testData = [{
    hello : 'hello',
    pathArr: []
}, {
    child : {
        hello : false
    },
    pathArr: ['child']
}, {
    child : {
        hello : null
    },
    pathArr: ['child']
}, {
    child: {
        child: {
            hell: 'hell'
        }
    },
    pathArr: ['child', 'child']
}];



testData.forEach(function (json) {
    console.log(findValue(json, json.pathArr, 'hello'));
});

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1074088

Fundamentally, all of the paths through your function must return a value. Currently, there's a path that doesn't, the path that takes the forEach:

jsonData.forEach(function(thisData) {
    findValue(thisData, pathArr, targetField);
});

There, you probably want some (so you can stop when you find it) and a variable that you can set to the found value, e.g:

var retval;

and

jsonData.some(function(thisData) {
    retVal = findValue(thisData, pathArr, targetField);
    return !!retVal; // Stops the `some` loop, doesn't exit `findValue`
});
if (retVal) {
    return retVal;
}

Side note: You don't want to declare result outside the function, there's no need. The function can be entirely self-contained. Here's a version that is self-contained, for instance:

function findValue(jsonData, pathArr, targetField) {
    var result = null;

    if (pathArr.length == 0) {
        result = targetField in jsonData ? jsonData[targetField] : null;
    } else {

        var curNode = pathArr.shift();

        if (curNode in jsonData) {
            jsonData = jsonData[curNode];
        }

        if (Array.isArray(jsonData)) {
            jsonData.some(function(thisData) {
                result = findValue(thisData, pathArr, targetField);
                return !!result; // Stops the `some` loop, doesn't exit `findValue`
            });
        } else {
            result = findValue(jsonData, pathArr, targetField);
        }
    }

    return result;
}

Note: I didn't do a full review of the code, just looked at return values.

Upvotes: 1

Related Questions