我有一个名为 Info 的结构,它是根据它收到的数据进行解码的。但有时,数据中的一个值可以是双精度值或双精度数组。如何为此设置结构?
struct Info: Decodable {
let author: String
let title: String
let tags: [Tags]
let price: [Double]
enum Tags: String, Decodable {
case nonfiction
case biography
case fiction
}
}
根据网址,我要么得到双倍的价格
{
"author" : "Mark A",
"title" : "The Great Deman",
"tags" : [
"nonfiction",
"biography"
],
"price" : "242"
}
或者我把它当作一个双打数组
{
"author" : "Mark A",
"title" : "The Great Deman",
"tags" : [
"nonfiction",
"biography"
],
"price" : [
"242",
"299",
"335"
]
}
我想设置我的结构,以便如果我收到双精度而不是双精度数组,价格应解码为 1 双精度数组。
您的 JSON 实际上要么是一个字符串,要么是一个字符串数组。因此,您需要创建一个自定义解码器进行解码,然后将其转换为Double:
struct Info {
let author, title: String
let tags: [Tags]
let price: [Double]
enum Tags: String, Codable {
case nonfiction, biography, fiction
}
}
extension Info: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
author = try container.decode(String.self, forKey: .author)
title = try container.decode(String.self, forKey: .title)
tags = try container.decode([Tags].self, forKey: .tags)
do {
price = try [Double(container.decode(String.self, forKey: .price)) ?? .zero]
} catch {
price = try container.decode([String].self, forKey: .price).compactMap(Double.init)
}
}
}
游乐场测试
let infoData = Data("""
{
"author" : "Mark A",
"title" : "The Great Deman",
"tags" : [
"nonfiction",
"biography"
],
"price" : "242"
}
""".utf8)
do {
let info = try JSONDecoder().decode(Info.self, from: infoData)
print("price",info.price) // "price [242.0]n"
} catch {
print(error)
}
let infoData2 = Data("""
{
"author" : "Mark A",
"title" : "The Great Deman",
"tags" : [
"nonfiction",
"biography"
],
"price" : [
"242",
"299",
"335"
]
}
""".utf8)
do {
let info = try JSONDecoder().decode(Info.self, from: infoData2)
print("price",info.price) // "price [242.0, 299.0, 335.0]n"
} catch {
print(error)
}
让我为我们可以处理Codable
中的一个或多个值的情况提出一个通用解决方案。
import Foundation
// Equatable confarmance is for checking correct encoding/decoding operations only
enum OneOrMany<U: Codable>: Codable, Equatable where U: Equatable {
case one(U)
case many([U])
// Decodable conformance
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(U.self) {
self = .one(x)
return
}
if let x = try? container.decode([U].self) {
self = .many(x)
return
}
throw DecodingError.typeMismatch(OneOrMany.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unexpected assosiated type for an enum"))
}
// Encodable conformance
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .one(let x):
try container.encode(x)
case .many(let x):
try container.encode(x)
}
}
}
这是一种在操场上测试它的方法:
struct Info: Codable, Equatable {
let author: String
let title: String
let tags: [Tags]
let price: OneOrMany<String>
enum Tags: String, Codable {
case nonfiction
case biography
case fiction
}
}
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()
let onePriceData = """
{
"author" : "Mark A",
"title" : "The Great Deman",
"tags" : [
"nonfiction",
"biography"
],
"price" : "242"
}
""".data(using: .utf8)!
let severalPricesData = """
{
"author" : "Mark A",
"title" : "The Great Deman",
"tags" : [
"nonfiction",
"biography"
],
"price" : [
"242",
"299",
"335"
]
}
""".data(using: .utf8)!
let onePrice = try decoder.decode(Info.self, from: onePriceData)
dump(onePrice)
let onePriceDataEncoded = try encoder.encode(onePrice)
print(String(data: onePriceDataEncoded, encoding: .utf8)!)
let onePrice2 = try decoder.decode(Info.self, from: onePriceDataEncoded)
let severalPrices = try decoder.decode(Info.self, from: severalPricesData)
let severalPricesDataEncoded = try encoder.encode(severalPrices)
let severalPrices2 = try decoder.decode(Info.self, from: severalPricesDataEncoded)
import XCTest
class JSONEncodeDecodeTestCase : XCTestCase {
func testOnePriceDecodedEncodedSuccessfully() {
XCTAssertEqual(onePrice, onePrice2)
}
func testSeveralPricesDecodedEncodedSuccessfully() {
XCTAssertEqual(severalPrices, severalPrices2)
}
}
JSONEncodeDecodeTestCase.defaultTestSuite.run()
旁注:在这里我们还可以利用StringBacked<Value: StringRepresentable>: Codable
来转换值,因为出于某种原因Double
值在提供的 JSON 中被编码为字符串。