Home > database >  How to deal with this JSON?
How to deal with this JSON?

Time:07-17

I have this JSON coming from server:

{
  "models": [
    {
      "code": "RPX",
      "name": "Tank",
      "color": null,
      "alias": [
        "tank"
      ],
      "plantCode": "FR",
      "countryName": "France",
      "plantAlias": null,
      "plantGroup": "5",
      "matrix": [
        {
          "size": "BIG",
          "union": null
        }
      ],
      "imageURL": "https://example.com/tank.jpg"
    }
  ]
}

from this JSON I have built this model:

import Foundation

// MARK: - Welcome
struct ModelDecode: Codable {
    let models: [Model]?
}

// MARK: - Model
struct Model: Codable {
    let code, name: String?
    let color: JSONNull?
    let alias: [String]?
    let plantCode, countryName: String?
    let plantAlias: JSONNull?
    let plantGroup: String?
    let matrix: [Matrix]?
    let imageURL: String?
}

// MARK: - Matrix
struct Matrix: Codable {
    let size: String?
    let union: JSONNull?
}

// MARK: - Encode/decode helpers

class JSONNull: Codable, Hashable {

    public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
        return true
    }

    public var hashValue: Int {
        return 0
    }

    public func hash(into hasher: inout Hasher) {
        // No-op
    }

    public init() {}

    public required init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if !container.decodeNil() {
            throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
        }
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encodeNil()
    }
}

Then I have this to decode the JSON:

final class ListOfTanks:NSObject, URLSessionDelegate {
  
  private let session: URLSession = NetworkService.shared.getSession()
  private let decoder = JSONDecoder()
  private let encoder = JSONEncoder()
  
  static let shared = ListOfTanks()
  
  static func serverURL() -> URL? {
   let getServerHost = "https://example.com/request"
   let getServerPath = "/getTanks"
    
    var components = URLComponents()
    components.scheme = "https"
    components.host = getServerHost
    components.path = getServerPath
    guard let url = components.url else { return nil }
    return url
  }
  
  
  static func getRequest() -> URLRequest? {
    guard let url = ListOfTanks.serverURL()
    else { return nil }
    
    var request: URLRequest = URLRequest(url: url)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue("service", forHTTPHeaderField: "iPad")
    request.httpMethod = "GET"
    return request
  }
  
  func getStations(completion:callback<ModelDecode>?) {
        
    guard let request = ListOfTanks.getRequest() else {
      completion?(.failure("Failed to build request"))
      return }
    
    let session: URLSession = URLSession(configuration: URLSessionConfiguration.default,
                                         delegate: self,
                                         delegateQueue: OperationQueue())
    
    let task = session.dataTask(with: request as URLRequest) { (data, response, error) -> Void in
      
      guard let data = data else {
        completion?(.failure("Error"))
        return
      }
      
      do {
        let result = try self.decoder.decode(ModelDecode.self, from: data)
        completion?(.success(result))
      } catch (let error) {
        print(error)
        completion?(.failure("Error Parsing Error \(String(describing:error))"))
      }
  
    }
    task.resume()
    
  }
  
}

the JSON is retrieved correctly but the last try for decoding fails with:

typeMismatch(MyApp.JSONNull, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "models", intValue: nil), _JSONKey(stringValue: "Index 1", intValue: 1), CodingKeys(stringValue: "matrix", intValue: nil), _JSONKey(stringValue: "Index 1", intValue: 1), CodingKeys(stringValue: "union", intValue: nil)], debugDescription: "Wrong type for JSONNull", underlyingError: nil))
failure("Error Parsing Error typeMismatch(MyApp.JSONNull, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: \"models\", intValue: nil), _JSONKey(stringValue: \"Index 1\", intValue: 1), CodingKeys(stringValue: \"matrix\", intValue: nil), _JSONKey(stringValue: \"Index 1\", intValue: 1), CodingKeys(stringValue: \"union\", intValue: nil)], debugDescription: \"Wrong type for JSONNull\", underlyingError: nil))")

How do I solve that?

NOTE: I have omitted some code that I think is implicit, once the JSON is being retrieved correctly. If you need some specific code, please ask.

CodePudding user response:

The first thing you should do is get rid of that JSONNull type. It´s not helping. This type will fail to decode as soon as the JSON value is something different then null. And a property that allways stays null seems kind of pointless.

Next step would be to replace the JSONNulls with their "real" type as an optional. Multiple approaches here

  • Get a better sample. As you seem to have created this structs by quicktype.io, paste a larger dataset into it. There´s a good chance this will give you the types you need.

  • Make an assumption. As all of the properties seem to be of type String it would be (kind of) safe to assume the missing ones are of the same type. Replace JSONNull with String?

  • Ask/Read. Read the docu or contact the owner/developer of the API and ask what types there are and wich one could get null.

  • Related