Welcome to Code Forum!

Join a community that supports you and your coding journey from day one. We strive to be a friendly, supportive community that empowers everyone to be better developers. By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!
  • Guest, before posting your code please take these rules into consideration:
    • It is required to use our BBCode feature to display your code. While within the editor click < / > or >_ and place your code within the BB Code prompt. This helps others with finding a solution by making it easier to read and easier to copy.
    • You can also use markdown to share your code. When using markdown your code will be automatically converted to BBCode. For help with markdown check out the markdown guide.
    • Don't share a wall of code. All we want is the problem area, the code related to your issue.

    GIF shows where to locate </> in the thread and or post editor toolbar.
    To learn more about how to use our BBCode feature, review our "How to post your code into threads" here.

    Thank you, Code Forum.

JavaScript Help with JSON

desktop

Coder
Hi please can anyone help I'm struggling to understand JSON. I need to return two variables from a json function for use with measuring water levels but the returned variables are undefined.

JSON:
$levelMarkers = getStationData(stationReference);
rangeTop =   $levelMarkers[0];
recordLevel = $levelMarkers[1];
use the returned values for other stuff.


function getStationData(stRef) {
        $.getJSON("https://environment.data.gov.uk/flood-monitoring/id/stations/"+stRef+"/stageScale", function(json){
             // collect the data
            var topOfNormalRange = json["items"]["typicalRangeHigh"];
            var maxRecord = json["items"]["maxOnRecord"]["value"];
                    maxRecord = maxRecord.toFixed(2);
                    topOfNormalRange = topOfNormalRange.toFixed(2);

                  var levels = [topOfNormalRange, maxRecord];
                 return (levels);
        });
}
 
Last edited by a moderator:
That's because getStationsData doesn't return any value. You can use a callback function or a promise to get the data:
JavaScript:
// Use a callback
function getStationData(stRef, myCallback) {
    $.getJSON(..., function(json) {
        ...
        myCallback([topOfNormalRange, maxRecord]);
    });
}
getStationData(stationReference, levelMarkers => {
    // Do something with levelMarkers
});

// Use a promise
function getStationData(stRef) {
    return new Promise(resolve => {
        $.getJSON(..., function(json) {
            ...
            resolve([topOfNormalRange, maxRecord]);
        });
    });
}
getStationData(stationReference).then(levelMarkers => {
    // Do something with levelMarkers
});
 
Thank you very much for the above which moves me forward but not to the place I need to be. I really want to use the captured variables globally. The function loops through all station references and correctly shows the result for each in the alert, but is it possible to use the variables maxOnRecord & topOfNormalRange outside of the function getStationData(stationReference)?

JavaScript:
getStationData(stationReference).then(levelMarkers => {
    var topOfNormalRange = levelMarkers[0];
    var maxOnRecord = levelMarkers[1];
    alert(maxOnRecord);
}); 
 if (currentLevel > topOfNormalRange)  {
    do stuff here with  topOfNormalRange & maxOnRecord
}
 
Last edited by a moderator:
Yes and no. You could do something like this:
JavaScript:
const capturedValues = [];
['ref1', 'ref2'].forEach(stationReference => {
    getStationData(stationReference).then(levelMarkers => {
        capturedValues.push(levelMarkers);
    });
});
console.log(capturedValues);
But because getStationData is an asynchronous function (and will run much later), the capturedValues will be empty in my example. What you can do, is move the code that uses the values inside getStationData(stationReference).then, or use Promise.all to capture values from all of the requests and use them at once like this:
JavaScript:
const arrayOfPromises = ['ref1', 'ref2'].map(stationReference =>
    getStationData(stationReference)
);
const combinerPromise = Promise.all(arrayOfPromises);

combinerPromise.then(capturedValues => {
    const topOfNormalRange1 = capturedValues[0][0];
    const maxOnRecord1      = capturedValues[0][1];

    const topOfNormalRange2 = capturedValues[1][0];
    const maxOnRecord2      = capturedValues[1][1];
});
Why you need the variables to be global?
 
"Why you need the variables to be global?"

Thank you again your help is much appreciated. I think I may have gone about this all the wrong way. The variable currentReading is created by a top level json loop. The maxOnRecord & topOfNormalRange data is contained in another json file which I thought could be extracted by running an loop within the first loop using the function getStationData(stationReference);

To get the responses required I need to compare currentReading against maxOnRecord (New record flood) and currentReading against topOfNormalRange (flooding likely)

I didn't write the json code that was kindly provided on another site so I don't really understand how it works. I suspect I need to combine both json files within a single array but haven't a clue how to do that.

function httpGet(url) { return new Promise(function (resolve, reject) { return $.getJSON(url, resolve) }); } var promises = upstreamLevels.map(function(u) { return httpGet("https://environment.data.gov.uk/flood-monitoring/id/measures/"+u+"-level-stage-i-15_min-m/readings?_view=full&_sorted&_limit=3") }) Promise.all(promises).then(function(promiseResp) { promiseResp.forEach( function(json) { stationReference = json["items"][0]["measure"]["stationReference"]; currentReading = json["items"][0]["value"]; ........................... //your code getStationData(stationReference).then(levelMarkers => { var topOfNormalRange = levelMarkers[0]; var maxOnRecord = levelMarkers[1]; }); // required outcome if (currentReading > maxOnRecord) { /// river reached new record flood cellColor = 'red'; ............................................... } else if (currentReading > topOfNormalRange) { /// flooding likely cellColor ='yellow'; ............................................... } //Second json file function getStationData(stRef) { $.getJSON("https://environment.data.gov.uk/flood-monitoring/id/stations/"+stRef+"/stageScale", function(json){ ...................................
 
So you need data from one request to make another one, and then use data from both of them? If that's the case, you can do it like this:
JavaScript:
const stationRef = 'ref';
// 1. Do the first request
httpGet('.../flood-monitoring/id/measures/'+stationRef+'-level-stage-...')
    .then(resp => {
        const currentReading = resp.items.latestReading.value;
// 2. Do the second request, and return a new promise that combines them
        return Promise.all([currentReading, getStationData(stationRef)]);
    })
// 3. Now you have data from both requests
    .then(([currentReading, levelMarkers]) => {
        const topOfNormalRange = levelMarkers[0];
        const maxOnRecord = levelMarkers[1];
        if (currentReading > maxOnRecord) {
            /// river reached new record flood
            cellColor = 'red';
        } else if (currentReading > topOfNormalRange) {
            //...
        }
        //...
    });
If you need to do this multiple times, you can use a loop:
JavaScript:
['ref', 'ref2'].forEach(stationRef => {
    httpGet('.../flood-monitoring/id/measures/'+stationRef+'-level-stage-...')
        .then(resp => {
        ... rest of the above's code
});
 
Thank you again that's moved it on to 99% of what's required. The remaining issue is the data for each level station now displays in random order in <div id="measures">...loading</div>
and needs to be listed in the order of the 'upstreamLevels [ ]' (station references) array.
Code:
var upstreamLevels = new Array("F1207", "L1231", "L1206", "L12041", "L1205", "L1203", "F1203", "L1008", "F1003", "L1108", "L1103", "F1102", "L1308", "L1307", );

I think this part of the old code did that

Code:
        Promise.all(promises)
        .then(function(promiseResp) {
        promiseResp.forEach( function(json) {
        
        ..................
        
        }

Is it possible to combine that into your code?

Code:
       function httpGet(url) {
                  return new Promise(function (resolve, reject) {
                    return $.getJSON(url, resolve)
                   });
                }
                
var promises = upstreamLevels.map(function(u) {
                    
return httpGet("https://environment.data.gov.uk/flood-monitoring/id/measures/"+u+"-level-stage-i-15_min-m/readings?_view=full&_sorted&_limit=3")
        .then(resp => {
                var currentReading = resp.items[0].value;
                var stationReference = resp.items[0].measure.stationReference;
                var label = resp.items[0].measure.station.label;
                var previousReading = resp.items[1].value;
                var timeRecorded = resp.items[0].dateTime;

// 2. Do the second request, and return a new promise that combines them
        return Promise.all([currentReading, getStationData(stationReference)])
        .then(([currentReading, levelMarkers]) => {
        var topOfNormalRange = levelMarkers[0];
        const maxOnRecord = levelMarkers[1];
        
    
                    if (currentReading.toFixed(2) > previousReading.toFixed(2)) {
                        direction = 1; // rising
                        arrow = arrows[1];
                    } else if (currentReading.toFixed(2) < previousReading.toFixed(2))  {
                        direction = 2; // falling
                        arrow = arrows[3];
                    } else {
                        direction = 3; // level
                        arrow = arrows[2];
                    }

                //Above record level
                    if (currentReading.toFixed(2) > maxOnRecord) {                                 
                                cellColor = colors[4];
                //1 metre above top of normal range level
                    } else if (currentReading.toFixed(2) > (topOfNormalRange+1)) {
                                cellColor = colors[3];
                //Top of normal range level
                    } else if (currentReading.toFixed(2) > topOfNormalRange) {
                                cellColor = colors[2];   
                //Normal range level
                    } else {
                                cellColor = colors[1];
                    }
                    
            // extract time from date string
                timeRecorded = timeRecorded.substr(11,5);
                 stationUrl = '<a href="'+upStreamStationUrls[stationReference]+'" target="_blank" style="color:#fff;">'+label+'</a>';
                  measuresTable = measuresTable + '<div class="row upstream-row" style="background-color:'+cellColor+'"><div class="col-md-4 col-sm-12 small">' + stationUrl + '</div><div class="col-md-2 col-sm-12 small"> ' + currentReading.toFixed(2)+'m</div><div class="col-md-2 col-sm-12 small">'+arrow+'</div><div class="col-md-2 col-sm-12 small">'+timeRecorded+'</div><div class="col-md-2 col-sm-12 small">'+maxOnRecord+'</div></div>';
                   $("#measures").html(measuresTable);   
            })
        })
    })
})
 
Well, you could build the measures table first, and update it after each request completes:
JavaScript:
const upstreamLevels = ['F1207', ...];
const outputElement = $('#measures');
upstreamLevels.forEach(stationRef => {
    outputElement.append('<div class="station-' + stationRef + '"></div>');
});
/*
Contents of the #measures at this point
<div id="measures">
    <div class="station-F1207"></div>
    <div class="station-..."></div>
    ...
</div>
*/
Now you can replace
JavaScript:
measuresTable = measuresTable + '<div class="row upstream-row ...
$("#measures").html(measuresTable);
With
JavaScript:
const singleRow = '<div class="row upstream-row ...
$('#measures .station-' + stationReference).html(singleRow);
After this you can ditch that last Promises.all completely, since the output is already updated in the inner thens'.
 
Last edited:
Absolutely brilliant thank you so much. All working 100%.

I couldn't find the 'Promises.all' to ditch so I placed the code above the loop and all seem to work okay
Code:
  upstreamLevels.forEach(stationRef => {
                    outputElement.append('<div class="station-' + stationRef + '"></div>');   
 });

    var promises = upstreamLevels.map(function(u) {
     
        return httpGet("https://environment.data.gov.uk/flood-monitoring/id/measures/"+u+"-level-stage-i-15_min-m/readings?_view=full&_sorted&_limit=3")
        .then(resp => {
                 //...........................................................
        }

 

Buy us a coffee!

Buy me a coffee.
Back
Top Bottom