【发布时间】:2021-06-29 04:12:30
【问题描述】:
我的 API 返回具有 2 种可能的数据集格式的 JSON 数据,我该如何解码?提前非常感谢您
结果.json
format 1:
{
product_name: "coffee"
product_code: "100010"
variant: [
flavour: [
{
flavour_name: "chocolate",
flavour_code: "C001",
},
{
flavour_name: "vanilla",
flavour_code: "C002",
}
],
size: [ { ... }, { ... } ]
]
}
format 2:
{
product_name: "bread",
product_code: "B123",
variant: [
portion: ["quarter", "half", "full"],
type: ["plain", "wheat", "oat"],
]
}
MenuViewModel.swift
class MenuDetailViewModel: ObservableObject, MenuDetailService {
var apiSession: APIService
@Published var detaildata: MenuDetailData?
var cancellables = Set<AnyCancellable>()
init(apiSession: APIService = APISession()) {
self.apiSession = apiSession
}
func getMenuDetail() {
let cancellable = self.getMenuDetail(menuCode: menuCode)
.sink(receiveCompletion: { result in
switch result {
case .failure(let error):
print("Handle error: \(error)")
case .finished:
break
}
}) { (detail) in
self.detaildata = detail.data
}
cancellables.insert(cancellable)
}
}
MenuDetailService.swift
protocol MenuDetailService {
var apiSession: APIService {get}
func getMenuDetail(menuCode: String) -> AnyPublisher<MenuDetailAPIResponse, APIError>
}
extension MenuDetailService {
func getMenuDetail(menuCode: String) -> AnyPublisher<MenuDetailAPIResponse, APIError> {
return apiSession.request(with: APIEndpoint.menuDetail(menuCode: menuCode))
.eraseToAnyPublisher()
}
}
APIService.swift
protocol APIService {
func request<T: Decodable>(with builder: RequestBuilder) -> AnyPublisher<T, APIError>
}
RequestBuilder.swift
protocol RequestBuilder {
var urlRequest: URLRequest {get}
}
APISession.swift
struct APISession: APIService {
func request<T>(with builder: RequestBuilder) -> AnyPublisher<T, APIError> where T: Decodable {
// 1
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
// 2
return URLSession.shared
.dataTaskPublisher(for: builder.urlRequest)
// 3
.receive(on: DispatchQueue.main)
// 4
.mapError { _ in .unknown }
// 5
.flatMap { data, response -> AnyPublisher<T, APIError> in
if let response = response as? HTTPURLResponse {
// let dataString = String(data: data, encoding: .utf8) {
// print("Response data string:\n \(dataString)")
// }
if (200...299).contains(response.statusCode) {
print(String(data: data, encoding: .utf8) ?? "")
// 6
return Just(data)
.decode(type: T.self, decoder: decoder)
.mapError {_ in .decodingError}
.eraseToAnyPublisher()
} else {
// 7
return Fail(error: APIError.httpError(response.statusCode))
.eraseToAnyPublisher()
}
}
return Fail(error: APIError.unknown)
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}
APIEndpoint.swift
enum APIEndpoint {
case menuDetail(menuCode: String)
}
extension APIEndpoint: RequestBuilder {
var urlRequest: URLRequest {
switch self {
case .menuDetail(let menuCode):
guard let url = URL(string: "API_URL/product/detail")
else {preconditionFailure("Invalid URL format")}
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(Constants.API_TOKEN, forHTTPHeaderField: "Authorization")
request.httpMethod = "POST"
let body: [String: Any] = ["brand": 1, "city": "", "menu_code": menuCode, "member_phone": ""]
let rb = try! JSONSerialization.data(withJSONObject: body)
request.httpBody = rb
return request
}
}
}
MenuDetailAPIResponse.swift
struct MenuDetailAPIResponse: Codable {
let data: MenuDetailData
}
MenuDetailData.swift
struct MenuDetailData: Codable, Identifiable {
let id = UUID()
let productName: String
let productCode: String
let variant: [MenuVariant]
}
struct MenuVariant: Codable, Identifiable, Hashable {
let id = UUID()
let flavour: [MenuFlavour]
let size: [MenuSize]
}
struct MenuFlavour: Codable, Identifiable, Hashable {
let id = UUID()
let flavourName: String
let flavourCode: String
}
【问题讨论】:
-
请让您的问题更加集中,这看起来像是一个与 MVVM 或 SwiftUI 无关的 json 解码问题。删除所有与您所询问的内容不直接相关的代码,并更改问题的标题,以便更好地反映您所询问的内容。
-
如果您有关于如何解码的问题,为什么要将所有这些其他代码包含在各种协议和请求构建器中?此外,您的模型不会映射到第二个变体,并且您没有说明需要如何映射。
-
尝试使用带有try catch表达式的解码器并为错误“DecodingError.typeMismatch”配置catch块。
标签: ios json swift xcode swiftui