Usar codificable con un valor que a veces es Int y otras veces String

Resuelto Nevin Jethmalani asked hace 6 años • 6 respuestas

Tengo una API que a veces devolverá un valor clave específico (en este caso id) en JSON como Int y otras veces devolverá ese mismo valor clave como String. ¿Cómo uso codificable para analizar ese JSON?

struct GeneralProduct: Codable {
    var price: Double!
    var id: String?
    var name: String!

    private enum CodingKeys: String, CodingKey {
        case price = "p"
        case id = "i"
        case name = "n"
    }

    init(price: Double? = nil, id: String? = nil, name: String? = nil) {
        self.price = price
        self.id = id
        self.name = name
    }
}

Sigo recibiendo este mensaje de error: Expected to decode String but found a number instead. La razón por la que devuelve un número es porque el campo de identificación está vacío y cuando el campo de identificación está vacío, de forma predeterminada devuelve 0 como una identificación que codificable se identifica como un número. Básicamente puedo ignorar la clave de identificación, pero codificable no me da la opción de ignorarla, que yo sepa. ¿Cuál sería la mejor manera de manejar esto?

Aquí está el JSON. es super sencillo

Laboral

{
  "p":2.12,
  "i":"3k3mkfnk3",
  "n":"Blue Shirt"
}

Error: debido a que no hay una identificación en el sistema, devuelve 0 como valor predeterminado, lo que codificable obviamente ve como un número opuesto a una cadena.

{
  "p":2.19,
  "i":0,
  "n":"Black Shirt"
}
Nevin Jethmalani avatar Dec 22 '17 11:12 Nevin Jethmalani
Aceptado
struct GeneralProduct: Codable {
    var price: Double?
    var id: String?
    var name: String?
    private enum CodingKeys: String, CodingKey {
        case price = "p", id = "i", name = "n"
    }
    init(price: Double? = nil, id: String? = nil, name: String? = nil) {
        self.price = price
        self.id = id
        self.name = name
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        price = try container.decode(Double.self, forKey: .price)
        name = try container.decode(String.self, forKey: .name)
        do {
            id = try String(container.decode(Int.self, forKey: .id))
        } catch DecodingError.typeMismatch {
            id = try container.decode(String.self, forKey: .id)
        }
    }
}

let json1 = """
{
"p":2.12,
"i":"3k3mkfnk3",
"n":"Blue Shirt"
}
"""

let json2 = """
{
"p":2.12,
"i":0,
"n":"Blue Shirt"
}
"""

do {
    let product = try JSONDecoder().decode(GeneralProduct.self, from: Data(json2.utf8))
    print(product.price ?? "nil")
    print(product.id ?? "nil")
    print(product.name ?? "nil")
} catch {
    print(error)
}

editar/actualizar :

También puede simplemente asignarle nilcuando idregrese su API 0:

do {
    let value = try container.decode(Int.self, forKey: .id)
    id = value == 0 ? nil : String(value)
} catch DecodingError.typeMismatch {
    id = try container.decode(String.self, forKey: .id)
}
Leo Dabus avatar Dec 22 '2017 04:12 Leo Dabus

Esta es una posible solución con MetadataType, lo bueno es que puede ser una solución general no GeneralProductsolo, sino para todos los que structtienen la misma ambigüedad:

struct GeneralProduct: Codable {
  var price:Double?
  var id:MetadataType?
  var name:String?

  private enum CodingKeys: String, CodingKey {
    case price = "p"
    case id = "i"
    case name = "n"
  }

  init(price:Double? = nil, id: MetadataType? = nil, name: String? = nil) {
    self.price = price
    self.id = id
    self.name = name
  }
}

enum MetadataType: Codable {
  case int(Int)
  case string(String)

  init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    do {
      self = try .int(container.decode(Int.self))
    } catch DecodingError.typeMismatch {
      do {
        self = try .string(container.decode(String.self))
      } catch DecodingError.typeMismatch {
        throw DecodingError.typeMismatch(MetadataType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload not of an expected type"))
      }
    }
  }

  func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    switch self {
    case .int(let int):
      try container.encode(int)
    case .string(let string):
      try container.encode(string)
    }
  }
}

esta es la prueba:

let decoder = JSONDecoder()
var json =  "{\"p\":2.19,\"i\":0,\"n\":\"Black Shirt\"}"
var product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
  print(id) // 0
}

json =  "{\"p\":2.19,\"i\":\"hello world\",\"n\":\"Black Shirt\"}"
product = try! decoder.decode(GeneralProduct.self, from: json.data(using: .utf8)!)
if let id = product.id {
  print(id) // hello world
}
mugx avatar Dec 22 '2017 04:12 mugx