Piyush
Piyush

Reputation: 599

Parse json inside another json string in swift4

I am trying to parse the json. But the problem is I have json inside to it another json String. Like :

{
    "count": 284,
    "next": "http://X:X:X:X:8080/api/sensor/last5feed?page=2&search=XXXXX",
    "previous": null,
    "results": [
        {
            "id": 571,
            "feed": "{'app_id': 'XXXXX', 'dev_id': 'XXXX', 'hardware_serial': 'XXXXX', 'port': 6, 'counter': 4290, 'payload_raw': 'AQEBIwXsF4IAAAA=', 'payload_fields': {'aamsg_type': 'weather', 'abstatus': 0, 'batteryV': 3.5, 'battery_low': 'no', 'humiP': 60.2, 'tempC': 15.2}, 'metadata': {'time': '2020-01-23T15:09:32.350967362Z', 'frequency': 868.1, 'modulation': 'LORA', 'data_rate': 'SF7BW125', 'airtime': 61696000, 'coding_rate': '4/5', 'gateways': [{'gtw_id': 'XXXXX', 'timestamp': 3227230963, 'time': '2020-01-23T15:09:32.326146Z', 'channel': 0, 'rssi': -98, 'snr': 4.8, 'rf_chain': 1, 'latitude': 57.124737, 'longitude': -2.1646452, 'altitude': 90, 'location_source': 'registry'}]}}",
            "created_at": "2020-01-23T15:09:32.630326Z",
            "sensor": 1
        },
        {
            "id": 569,
            "feed": "{'app_id': 'XXXXXX', 'dev_id': 'XXXX', 'hardware_serial': 'XXXX', 'port': 6, 'counter': 4289, 'payload_raw': 'XXXXX', 'payload_fields': {'aamsg_type': 'weather', 'abstatus': 0, 'batteryV': 3.5, 'battery_low': 'no', 'humiP': 57.6, 'tempC': 16.9}, 'metadata': {'time': '2020-01-23T14:09:32.132070865Z', 'frequency': 867.3, 'modulation': 'LORA', 'data_rate': 'SF7BW125', 'airtime': 61696000, 'coding_rate': '4/5', 'gateways': [{'gtw_id': 'XXXXXX', 'timestamp': 3921981659, 'time': '2020-01-23T14:09:32.104672Z', 'channel': 4, 'rssi': -107, 'snr': 8.2, 'rf_chain': 0, 'latitude': 57.124737, 'longitude': -2.1646452, 'altitude': 90, 'location_source': 'registry'}]}}",
            "created_at": "2020-01-23T14:09:32.448929Z",
            "sensor": 1
        }
}

I am getting the values till feed. But I am not able to parse further, My code is :

if(status_code == 200){
  if let json = response.data {
    do{
      let data = try JSON(data: json)
      let result = data["results"].arrayObject! as NSArray
      let ct = result.count
       if(ct != 0 ) {
       self.noDataFound.isHidden = true
       for i in 0...ct-1 {
          let data = result[i] as? NSDictionary
          let feed = data?.value(forKey: "feed") as? NSString
          let data3 = try JSON(data: feed as! Data) . 
          print(data3)
        }
    }
} catch {} }}

I need to get the hardware_serial from feed. Can any body please help me what i am doing wrong here!! Thanks!!!

Upvotes: 0

Views: 216

Answers (3)

Rob Napier
Rob Napier

Reputation: 299425

First, peel off the piece you want:

struct ResultWrapper: Decodable {
    let results: [Result]
}

And then build a custom decoder to extract feed, which is malformed. Single-quotes are not legal JSON. As a hack, the following code just substitutes all single-quotes with double-quotes, but this won't work if there are any embedded quoted-single-quotes.

struct Result: Decodable {
    enum CodingKeys: String, CodingKey { case feed }
    let feed: Feed
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        // inner JSON is mal-formed. Have to fix it.
        let feedString = try container.decode(String.self, forKey: .feed)
            .replacingOccurrences(of: "'", with: "\"")

        feed = try JSONDecoder().decode(Feed.self, from: Data(feedString.utf8))
    }
}

Finally, decoding Feed is mechanical, but requires custom CodingKeys. I recommend quicktype for that:

// https://app.quicktype.io?share=7O6iNJ3ugXb4mr84TIoG
struct Feed: Codable {
    var appID, devID, hardwareSerial: String
    var port, counter: Int
    var payloadRaw: String
    var payloadFields: PayloadFields
    var metadata: Metadata

    enum CodingKeys: String, CodingKey {
        case appID = "app_id"
        case devID = "dev_id"
        case hardwareSerial = "hardware_serial"
        case port, counter
        case payloadRaw = "payload_raw"
        case payloadFields = "payload_fields"
        case metadata
    }
}

// MARK: - Metadata
struct Metadata: Codable {
    var time: String
    var frequency: Double
    var modulation, dataRate: String
    var airtime: Int
    var codingRate: String
    var gateways: [Gateway]

    enum CodingKeys: String, CodingKey {
        case time, frequency, modulation
        case dataRate = "data_rate"
        case airtime
        case codingRate = "coding_rate"
        case gateways
    }
}

// MARK: - Gateway
struct Gateway: Codable {
    var gtwID: String
    var timestamp: Int
    var time: String
    var channel, rssi: Int
    var snr: Double
    var rfChain: Int
    var latitude, longitude: Double
    var altitude: Int
    var locationSource: String

    enum CodingKeys: String, CodingKey {
        case gtwID = "gtw_id"
        case timestamp, time, channel, rssi, snr
        case rfChain = "rf_chain"
        case latitude, longitude, altitude
        case locationSource = "location_source"
    }
}

// MARK: - PayloadFields
struct PayloadFields: Codable {
    var aamsgType: String
    var abstatus: Int
    var batteryV: Double
    var batteryLow: String
    var humiP, tempC: Double

    enum CodingKeys: String, CodingKey {
        case aamsgType = "aamsg_type"
        case abstatus, batteryV
        case batteryLow = "battery_low"
        case humiP, tempC
    }
}

Upvotes: 0

vadian
vadian

Reputation: 285140

  1. The string for key feed is not valid JSON. You have to replace the single quotes with double quotes.
  2. Create a Data object from the string (casting the type doesn't work).
  3. Create a JSON object from the data.
  4. Get the values you need.

Side note:

Don't use NS... collection types and NSString in Swift.

Upvotes: 1

PGDev
PGDev

Reputation: 24341

Use Codable to parse the above JSON response.

Models:

struct Root: Codable {
    let count: Int
    let next: String
    let previous: String?
    let results: [Result]
}

struct Result: Codable {
    let id: Int
    let feed, createdAt: String
    let sensor: Int
}

Parse the JSON data like so,

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let response = try decoder.decode(Root.self, from: data)
    print(response)
} catch {
    print(error)
}

Upvotes: 0

Related Questions