从 JSON 中解析十进制显示为字符串
Parsing Decimal from JSON presented as string
使用 Xcode 10.2 和 iOS 12.x 我们能够从 json 字符串中提取小数。 Xcode 11.1 和 iOS 13.1 抛出异常
Expected to decode Double but found a string/data instead.
class MyClass : Codable {
var decimal: Decimal?
}
然后尝试解析它
let json = "{\"decimal\":\"0.007\"}"
let data = json.data(using: .utf8)
let decoder = JSONDecoder()
decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "s1", negativeInfinity: "s2", nan: "s3")
do {
let t = try decoder.decode(MyClass.self, from: data!)
} catch {
print(error)
}
如果我将 json 字符串更改为
let json = "{\"decimal\":0.007}"
它有效,但我们又一次失去了精度。有什么想法吗?
我认为更简洁的解决方案是声明值,而不是像字符串那样声明值:
"test": 0.007
具有这样的结构:
struct Stuff {
var test: Decimal
}
然后:
let decoder = JSONDecoder()
let stuff = try decoder.decode(Stuff.self, from: json)
否则你可以使用这个例子:
https://forums.swift.org/t/parsing-decimal-values-from-json/6906/3
该解码策略与将数字表示为字符串无关。你需要做的是实现 init(from:)
并从那里的字符串转换
class MyClass : Codable {
var decimal: Double?
enum CodingKeys: String, CodingKey {
case decimal = "test"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
decimal = Double(try container.decode(String.self, forKey: .decimal)
//or if Decimal is used:
//decimal = Decimal(string: try container.decode(String.self, forKey: .decimal)
}
}
请注意,我在这里使用 Double 而不是 Decimal 以使其更简单
struct Root: Codable {
let decimal: Decimal
}
extension Root {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
decimal = try Decimal(string: container.decode(String.self, forKey: .decimal)) ?? .zero
}
}
let json = #"{"decimal":"0.007"}"#
do {
let root = try JSONDecoder().decode(Root.self, from: .init(json.utf8))
print(root)
} catch {
print(error)
}
这将打印
Root(decimal: 0.007)
类型应该是 Double 并且在解析中也定义为 Double。 Swift 会弄清楚剩下的
struct MyClass: Decodable {
let decimal: Double
//can be renamed to follow the API name.
enum CodingKeys: String, CodingKey {
case decimal
}
}
extension MyClass {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
decimal = try values.decode(Double.self, forKey: .decimal)
}
}
您需要扩展 KeyedDecodingContainer 并为 Decimal.Type 添加一个实现。
extension KeyedDecodingContainer {
func decode(_ type: Decimal.Type, forKey key: K) throws -> Decimal {
let stringValue = try decode(String.self, forKey: key)
guard let decimalValue = Decimal(string: stringValue) else {
let context = DecodingError.Context(codingPath: [key], debugDescription: "The key \(key) couldn't be converted to a Decimal value")
throw DecodingError.typeMismatch(type, context)
}
return decimalValue
}
}
这是一个例子:
let json = """
{
"capAmount": "123.45"
}
"""
struct Status: Decodable {
let capAmount: Decimal
enum CodingKeys: String, CodingKey {
case capAmount
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
capAmount = try container.decode(Decimal.self, forKey: .capAmount)
}
}
// Execute it
if let data = json.data(using: .utf8){
let status = try JSONDecoder().decode(Status.self, from: data)
print(status.capAmount)
}
使用 Xcode 10.2 和 iOS 12.x 我们能够从 json 字符串中提取小数。 Xcode 11.1 和 iOS 13.1 抛出异常
Expected to decode Double but found a string/data instead.
class MyClass : Codable {
var decimal: Decimal?
}
然后尝试解析它
let json = "{\"decimal\":\"0.007\"}"
let data = json.data(using: .utf8)
let decoder = JSONDecoder()
decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "s1", negativeInfinity: "s2", nan: "s3")
do {
let t = try decoder.decode(MyClass.self, from: data!)
} catch {
print(error)
}
如果我将 json 字符串更改为
let json = "{\"decimal\":0.007}"
它有效,但我们又一次失去了精度。有什么想法吗?
我认为更简洁的解决方案是声明值,而不是像字符串那样声明值:
"test": 0.007
具有这样的结构:
struct Stuff {
var test: Decimal
}
然后:
let decoder = JSONDecoder()
let stuff = try decoder.decode(Stuff.self, from: json)
否则你可以使用这个例子:
https://forums.swift.org/t/parsing-decimal-values-from-json/6906/3
该解码策略与将数字表示为字符串无关。你需要做的是实现 init(from:)
并从那里的字符串转换
class MyClass : Codable {
var decimal: Double?
enum CodingKeys: String, CodingKey {
case decimal = "test"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
decimal = Double(try container.decode(String.self, forKey: .decimal)
//or if Decimal is used:
//decimal = Decimal(string: try container.decode(String.self, forKey: .decimal)
}
}
请注意,我在这里使用 Double 而不是 Decimal 以使其更简单
struct Root: Codable {
let decimal: Decimal
}
extension Root {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
decimal = try Decimal(string: container.decode(String.self, forKey: .decimal)) ?? .zero
}
}
let json = #"{"decimal":"0.007"}"#
do {
let root = try JSONDecoder().decode(Root.self, from: .init(json.utf8))
print(root)
} catch {
print(error)
}
这将打印
Root(decimal: 0.007)
类型应该是 Double 并且在解析中也定义为 Double。 Swift 会弄清楚剩下的
struct MyClass: Decodable {
let decimal: Double
//can be renamed to follow the API name.
enum CodingKeys: String, CodingKey {
case decimal
}
}
extension MyClass {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
decimal = try values.decode(Double.self, forKey: .decimal)
}
}
您需要扩展 KeyedDecodingContainer 并为 Decimal.Type 添加一个实现。
extension KeyedDecodingContainer {
func decode(_ type: Decimal.Type, forKey key: K) throws -> Decimal {
let stringValue = try decode(String.self, forKey: key)
guard let decimalValue = Decimal(string: stringValue) else {
let context = DecodingError.Context(codingPath: [key], debugDescription: "The key \(key) couldn't be converted to a Decimal value")
throw DecodingError.typeMismatch(type, context)
}
return decimalValue
}
}
这是一个例子:
let json = """
{
"capAmount": "123.45"
}
"""
struct Status: Decodable {
let capAmount: Decimal
enum CodingKeys: String, CodingKey {
case capAmount
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
capAmount = try container.decode(Decimal.self, forKey: .capAmount)
}
}
// Execute it
if let data = json.data(using: .utf8){
let status = try JSONDecoder().decode(Status.self, from: data)
print(status.capAmount)
}