Parsing JSON with similar identifiers

Hi,

Im trying to parse JSON data from an RestAPI that gives me energy data for Norway (https://driftsdata.statnett.no/restapi/ProductionConsumption/GetLatestDetailedOverview)

I manage to read the JSON response fine, but Im having trouble "digging down" to the correct spot since the identifiers are the same for the different regions. Let say I wanted the data for the Hydro production below (see screenshot). How would i get that? Ive tries setting the [titleTranslationId] == "ProductionConsumption.HydroSEDesc" but that didnt work.

Anyone able to help? Heres my code:

    func getEnergyData(url: String){

    AF.request(url, method: .get).responseJSON{ response in
        switch response.result {
        case .success(let json):
        
            print("json success")
            //print(json)
            let energyJSON : JSON = JSON(response.result)
            self.updateEnergyData(json: energyJSON)
       
        
        case .failure(let error):
            print(error)

        }
        
    }
    
    
        
    }

trying to parse it:

func updateEnergyData(json : JSON){
            
    if let results = json["ProductionConsumptionOverviewViewModel"]["HydroData"]["ProdConsOverViewModelItem"]["value"].double{
        print(results)

    }
    else{
        print("parse fail")
    }
 
    }
        

   }

This format looks like XML, not JSON.

Also, you might get better answers by posting to a more relevant forum category. This post isn't about compiler development.

I know it reads like XML but the documentation said JSON so here goes. Im having the same issues reading it as XML still so.

To get around the similar-looking identifiers, I think you should filter the array to select the region you want. For example:

let request = URLRequest(url: URL(string: "https://driftsdata.statnett.no/restapi/ProductionConsumption/GetLatestDetailedOverview")!)

let cancelHandle = URLSession.shared.dataTaskPublisher(for: request)
    .map { $0.data }
    .decode(type: EnergyResponse.self, decoder: JSONDecoder())
    .sink { _ in } receiveValue: { energyResponses in
        print(energyResponses.hydroConsumption
            .filter { $0.id == "ProductionConsumption.HydroSEDesc" })
    }

struct EnergyResponse: Decodable {
    var hydroConsumption: [Consumption]
    
    struct Consumption: Decodable {
        var id: String?
        var value: String
        
        enum CodingKeys: String, CodingKey {
            case id = "titleTranslationId"
            case value
        }
    }
    
    enum CodingKeys: String, CodingKey {
        case hydroConsumption = "HydroData"
    }
}

The most relevant line being:

energyResponses.hydroConsumption
    .filter { $0.id == "ProductionConsumption.HydroSEDesc" }

As an aside, I'm not sure which JSON parsing API you're using. The example above uses the Codable APIs. The general approach should be the same either way: find the array that contains the different consumption values and filter it to get just the value for the region you're looking for.

tnx kyle! i was thinking about filtering the data as well somehow just didnt know how.

im getting errors relating to the .map and .sink arguments, do i need to import any class?

Referencing instance method 'sink(receiveValue:)' on 'Publisher' requires the types 'Error' and 'Never' be equivalent

I wrote the above using Xcode 12 beta, so there might be a few things to adapt if you're using an earlier version. You might need to import Combine, add .assertNoFailure() after the .decode line, and revert to the trailing closure syntax before multiple trailing closures:

.sink(receiveCompletion: { _ in }) { energyResponses in

great, that solved (parts of) it. now im lest with another error:
Error Domain=NSURLErrorDomain Code=-999 "cancelled"**

but im guessing this has to do with the data im getting back or that the data is corrupt?

That error is related to the lifetime of the cancelHandle constant. I wrote the example in a blank macOS playground. If, for example, you declared these variables in a button's tap handler, you'll need to extract the declaration of cancelHandle to a broader scope than the handler's closure. You could change it to be a global variable:

var cancelHandle: AnyCancellable? = nil

…then assign it to the return value of sink when you make the network call.

If we go back to Alamofire to simplify the example, you can achieve parse your response with the type @krilnon described:

AF.request(url).responseDecodable(of: EnergyResponse.self) { response in
    // You can inspect all of the response properties, including the Result of parsing it into EnergyResponse.
} 

You can then use the techniques described to get the data you want. If you want to use Combine, you can use Alamofire's integration:

AF.request(url).publishDecodable(type: EnergyResponse.self).value()

The .value() operator takes the DataResponse you'd normally get from Alamofire and breaks it into a value stream, like you'd get from the previous Combine examples. Also, like those examples, you need subscribe through sink or another operator and handle the cancellation token. However, I would recommend starting with the simpler completion handler and only moving to the Combine-based solution if you're going to be using Combine.

2 Likes

I actually soved it using the following:

let url = URL(string: "https://driftsdata.statnett.no/restapi/ProductionConsumption/GetLatestDetailedOverview")!
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else {
                print("No data")
                return
            }
            do {
                let result = try JSONDecoder().decode(HydroResult.self, from: data)
                if let seDesc = result.HydroData.filter({ $0.titleTranslationId == "ProductionConsumption.HydroSEDesc" }).first {

                    print(seDesc.value)
                    
                    
                } else {
                    print("Error: no value")
                }
            } catch {
                print(error.localizedDescription)
            }
        }
        task.resume()    }