当前位置: 首页 > 知识库问答 >
问题:

使用可编码的值,有时是Int,有时是String

李捷
2023-03-14

我有一个API,它有时会将JSON中的特定键值(在本例中为<code>id</code>)作为Int返回,而其他时候它会将相同的键值作为String返回。我如何使用codible解析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
    }
}

我不断收到此错误消息:预期解码字符串,但找到了一个数字。它返回数字的原因是 id 字段为空,当 id 字段为空时,它默认返回 0 作为可编码标识为数字的 ID。我基本上可以忽略 ID 键,但据我所知,可编码并没有给我忽略它的选项。处理这个问题的最佳方法是什么?

这是JSON。它超级简单

加工

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

错误-因为系统中没有id,所以它返回0作为默认值,codable显然将其视为数字而不是字符串。

{
  "p":2.19,
  "i":0,
  "n":"Black Shirt"
}

共有3个答案

卫建义
2023-03-14

IntString无缝解码为相同的属性需要编写一些代码。

但是,由于语言的(某种程度上)新添加(属性包装器),您可以在需要的任何地方轻松重用此逻辑:

// note this is only `Decodable`
struct GeneralProduct: Decodable {
    var price: Double
    @Flexible var id: Int // note this is an Int
    var name: String
}

属性包装器及其支持代码可以像这样实现:

@propertyWrapper struct Flexible<T: FlexibleDecodable>: Decodable {
    var wrappedValue: T
    
    init(from decoder: Decoder) throws {
        wrappedValue = try T(container: decoder.singleValueContainer())
    }
}

protocol FlexibleDecodable {
    init(container: SingleValueDecodingContainer) throws
}

extension Int: FlexibleDecodable {
    init(container: SingleValueDecodingContainer) throws {
        if let int = try? container.decode(Int.self) {
            self = int
        } else if let string = try? container.decode(String.self), let int = Int(string) {
            self = int
        } else {
            throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "Invalid int value"))
        }
    }
}

您可以在知道如何从任何基本JSON数据类型解码的字符串上使用包装器:字符串、数字、布尔值:

struct RelaxedString: Codable {
    let value: String
    
    init(_ value: String) {
        self.value = value
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        // attempt to decode from all JSON primitives
        if let str = try? container.decode(String.self) {
            value = str
        } else if let int = try? container.decode(Int.self) {
            value = int.description
        } else if let double = try? container.decode(Double.self) {
            value = double.description
        } else if let bool = try? container.decode(Bool.self) {
            value = bool.description
        } else {
            throw DecodingError.typeMismatch(String.self, .init(codingPath: decoder.codingPath, debugDescription: ""))
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(value)
    }
}

然后可以在结构中使用此新类型。一个小缺点是结构的使用者需要进行另一个间接访问来访问包装的字符串。但是,可以通过将解码的<code>RelaxedString</code>属性声明为private来避免这种情况,并为公共接口使用一个计算的属性:

struct GeneralProduct: Codable {
    var price: Double!
    var _id: RelaxedString?
    var name: String!
    
    var id: String? {
        get { _id?.value }
        set { _id = newValue.map(RelaxedString.init) }
    }

    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.map(RelaxedString.init)
        self.name = name
    }
}

上述方法的优点:

  1. 无需编写自定义 init(来自解码器:解码器)代码,如果要解码的属性数量增加,代码可能会变得乏味
  2. 可重用性 - RelaxedString 可以无缝地用于其他结构
  3. id可以从字符串或int解码的事实仍然是一个实现细节,GeneralProduct 的消费者不知道/关心 id 可以来自字符串或 int
  4. 公共接口公开字符串值,这使使用者代码保持简单,因为它不必处理多种类型的数据
陆文博
2023-03-14

这是< code >元数据类型的一个可能的解决方案,好的一面是它不仅是< code >一般产品的通用解决方案,也是所有具有相同歧义的< code >结构的通用解决方案:

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)
    }
  }
}

这是测试:

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
}
慕乐池
2023-03-14
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)
}

编辑/更新:

当api返回< code>0时,您也可以简单地将< code>nil赋给您的< code>id:

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)
}
 类似资料:
  • 问题内容: 我有一个API,有时会以Int的形式在JSON中返回特定的键值(在本例中为),而有时它会返回与String相同的键值。如何使用可编码解析该JSON? 我不断收到此错误消息:。它返回数字的原因是因为id字段为空,并且当id字段为空时,默认情况下返回0作为ID,可编码的ID标识为数字。我基本上可以忽略ID密钥,但是codable并不能让我选择忽略它。处理此问题的最佳方法是什么? 这是JSO

  • 问题内容: 我似乎找不到最大长度为max值的Java 。 是否存在? 如果是这样,在哪里? 问题答案: 正如@afsantos所说,由于Java数组的限制,该类固有地限于条目。 没有此限制,但是(尽管如此)昂贵: 与基于数组表示形式的一个引用相比,每个条目都会产生2个引用加上对象标头大小的内存开销。 与基于数组的列表相比,索引是一项操作。 这是指向Java库的链接,该库使用直接映射的内存和/或元素

  • 问题内容: 我知道Java具有双精度陷阱,但是为什么有时逼近结果还可以,但有时却不然。 像这样的代码: 结果是这样的: 问题答案: 如您所述,并非所有数字都可以在IEEE754中准确表示。结合Java用于打印这些数字的规则,这会影响您所看到的内容。 对于背景,我将简要介绍IEEE754的不准确性。在这种情况下,由于无法准确表示,因此您经常会发现实际使用的数字是。 请参阅此处以分析其原因。您获得“不

  • 问题内容: 我当然熟悉和类。但是,我只需要HTML样式的编码。(我不想替换为,等等)。我不知道任何内置JDK的类都可以进行HTML编码。有一个吗?我知道其他选择(例如,Jakarta Commons Lang’StringEscapeUtils’ ,但是我不想在需要此项目的项目中添加另一个外部依赖项。 我希望在最近的JDK(又名5或6)中添加一些可以执行我不知道的操作的东西。否则我必须自己动手。

  • 问题内容: 假设我有一个这样的课程: 我的应用程序中的许多其他类都在使用这些选项。现在,我只想更改其中一个选项,而仅部署已编译的类。但是,如果将这些字段插入到消费者类中,这将变得不可能,对吗? 是否有任何选项可以禁用内联编译时间常数? 问题答案: 您可以使用String.intern()获得所需的效果,但应注释您的代码,因为对此知之甚少。即 这样可以防止内联编译时间。由于它是指编译器将放入烫发中的

  • 问题内容: 检索的对象。此方法可以返回的所有可能值是什么?我可以使用该值来标识数据库中发生的特定错误吗(即,该值可以告诉我是否是PK违例,唯一约束或列值是否很大等)? 同样,该方法应该指示返回的是X / Open(现在称为Open Group)SQL CLI还是SQL99。唯一可能的价值应该是并且我正在获取价值。我想念什么吗? 有没有一种方法可以使用上述方法的组合来确定数据库中发生的特定错误类型?

  • 我正在尝试读取包含以下字段和值的csv文件: 引用字段是唯一的,选择字段可能为空。在此场景中,对于我的实体定义: 1.我应该为字段使用吗,因为它在csv文件中可能为空(空字符串)? 2.我应该使字段PK还是即使它是唯一的,我认为最好使用id字段并为字段添加。对吗? 3、对于开始日期字段,我认为应该使用本地日期,而不是日期,因为它已被弃用。这是真的吗?

  • 使用将转换为时,显示以下错误 [aac@0x2b4b640]编码器“aac”是实验性的,但未启用实验性编解码器,如果要使用它,请添加“-strict-2”。