Initial commit: suixinkan_ios UIKit rewrite project.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 14:33:31 +08:00
commit 9edf993432
297 changed files with 47151 additions and 0 deletions

View File

@ -0,0 +1,835 @@
//
// OrderModels.swift
// suixinkan
//
// Created by Codex on 2026/6/22.
//
import Foundation
/// Tab
enum OrdersEntry: Hashable {
case storeOrders
case verificationOrders
}
///
struct OrderStatusFilter: Identifiable, Hashable {
let id: Int
let title: String
}
///
enum OrderFilters {
static let statusFilters: [OrderStatusFilter] = [
.init(id: 0, title: "全部"),
.init(id: 18, title: "已付定金"),
.init(id: 30, title: "已完成"),
.init(id: 50, title: "已退款"),
.init(id: -1, title: "需要精修"),
.init(id: -2, title: "无需精修")
]
}
///
struct OrderEntity: Decodable, Identifiable, Equatable, Hashable {
var id: String { orderNumber }
let photogUid: Int
let orderNumber: String
let remark: String
let scenicAreaId: Int
let payTime: String
let completeTime: String
let orderStatus: Int
let orderStatusName: String
let storeId: Int?
let userId: Int
let projectId: Int
let phone: String
let orderAmount: String
let actualPayAmount: String
let actualRefundAmount: String
let refundAmount: String
let payTypeName: String
let payType: Int
let projectName: String
let orderType: Int
let orderTypeLabel: String
let depositPayTime: String
let createdAt: String
let photoTravel: OrderPhotoTravel?
let isNeedEdit: Bool
let isRefined: Int
enum CodingKeys: String, CodingKey {
case photogUid = "photog_uid"
case orderNumber = "order_number"
case remark
case scenicAreaId = "scenic_area_id"
case payTime = "pay_time"
case completeTime = "complete_time"
case orderStatus = "order_status"
case orderStatusName = "order_status_name"
case storeId = "store_id"
case userId = "user_id"
case projectId = "project_id"
case phone
case orderAmount = "order_amount"
case actualPayAmount = "actual_pay_amount"
case actualRefundAmount = "actual_refund_amount"
case refundAmount = "refund_amount"
case payTypeName = "pay_type_name"
case payType = "pay_type"
case projectName = "project_name"
case orderType = "order_type"
case orderTypeLabel = "order_type_label"
case depositPayTime = "deposit_pay_time"
case createdAt = "created_at"
case photoTravel = "photo_travel"
case isNeedEdit
case isRefined = "is_refined"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
photogUid = try container.decodeLossyInt(forKey: .photogUid) ?? 0
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
remark = try container.decodeLossyString(forKey: .remark)
scenicAreaId = try container.decodeLossyInt(forKey: .scenicAreaId) ?? 0
payTime = try container.decodeLossyString(forKey: .payTime)
completeTime = try container.decodeLossyString(forKey: .completeTime)
orderStatus = try container.decodeLossyInt(forKey: .orderStatus) ?? 0
orderStatusName = try container.decodeLossyString(forKey: .orderStatusName)
storeId = try container.decodeLossyInt(forKey: .storeId)
userId = try container.decodeLossyInt(forKey: .userId) ?? 0
projectId = try container.decodeLossyInt(forKey: .projectId) ?? 0
phone = try container.decodeLossyString(forKey: .phone)
orderAmount = try container.decodeLossyString(forKey: .orderAmount)
actualPayAmount = try container.decodeLossyString(forKey: .actualPayAmount)
actualRefundAmount = try container.decodeLossyString(forKey: .actualRefundAmount)
refundAmount = try container.decodeLossyString(forKey: .refundAmount)
payTypeName = try container.decodeLossyString(forKey: .payTypeName)
payType = try container.decodeLossyInt(forKey: .payType) ?? 0
projectName = try container.decodeLossyString(forKey: .projectName)
orderType = try container.decodeLossyInt(forKey: .orderType) ?? 0
orderTypeLabel = try container.decodeLossyString(forKey: .orderTypeLabel)
depositPayTime = try container.decodeLossyString(forKey: .depositPayTime)
createdAt = try container.decodeLossyString(forKey: .createdAt)
photoTravel = try container.decodeIfPresent(OrderPhotoTravel.self, forKey: .photoTravel)
isNeedEdit = try container.decodeLossyBool(forKey: .isNeedEdit) ?? true
isRefined = try container.decodeLossyInt(forKey: .isRefined) ?? 0
}
}
///
struct OrderPhotoTravel: Decodable, Equatable, Hashable {
let orderPhotoNum: Int
let orderVideoNum: Int
let checkInTime: String
let retouchGiftPhotoNum: Int
let retouchGiftVideoNum: Int
enum CodingKeys: String, CodingKey {
case orderPhotoNum = "order_photo_num"
case orderVideoNum = "order_video_num"
case checkInTime = "check_in_time"
case retouchGiftPhotoNum = "retouch_gift_photo_num"
case retouchGiftVideoNum = "retouch_gift_video_num"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
orderPhotoNum = try container.decodeLossyInt(forKey: .orderPhotoNum) ?? 0
orderVideoNum = try container.decodeLossyInt(forKey: .orderVideoNum) ?? 0
checkInTime = try container.decodeLossyString(forKey: .checkInTime)
retouchGiftPhotoNum = try container.decodeLossyInt(forKey: .retouchGiftPhotoNum) ?? 0
retouchGiftVideoNum = try container.decodeLossyInt(forKey: .retouchGiftVideoNum) ?? 0
}
}
///
struct WriteOffOrderItem: Decodable, Identifiable, Equatable, Hashable {
var id: String { orderNumber }
let orderNumber: String
let orderVerificationStatus: String
let payTime: String
let projectName: String
let userPhone: String
let orderAmount: String
let orderStatusName: String
let orderVerificationTime: String
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case orderVerificationStatus = "order_verification_status"
case payTime = "pay_time"
case projectName = "project_name"
case userPhone = "user_phone"
case orderAmount = "order_amount"
case orderStatusName = "order_status_name"
case orderVerificationTime = "order_verification_time"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
orderVerificationStatus = try container.decodeLossyString(forKey: .orderVerificationStatus)
payTime = try container.decodeLossyString(forKey: .payTime)
projectName = try container.decodeLossyString(forKey: .projectName)
userPhone = try container.decodeLossyString(forKey: .userPhone)
orderAmount = try container.decodeLossyString(forKey: .orderAmount)
orderStatusName = try container.decodeLossyString(forKey: .orderStatusName)
orderVerificationTime = try container.decodeLossyString(forKey: .orderVerificationTime)
}
}
///
struct WriteOffRequest: Encodable {
let orderNumber: String
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
}
}
///
struct DepositOrderListItem: Decodable, Identifiable, Equatable, Hashable {
let id: Int
let orderNumber: String
let userPhone: String
let amount: String
let status: Int
let statusName: String
let createdAt: String
enum CodingKeys: String, CodingKey {
case id
case orderNumber = "order_number"
case userPhone = "user_phone"
case amount
case status
case statusName = "status_name"
case createdAt = "created_at"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeLossyInt(forKey: .id) ?? 0
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
userPhone = try container.decodeLossyString(forKey: .userPhone)
amount = try container.decodeLossyString(forKey: .amount)
status = try container.decodeLossyInt(forKey: .status) ?? 0
statusName = try container.decodeLossyString(forKey: .statusName)
createdAt = try container.decodeLossyString(forKey: .createdAt)
}
}
///
struct DepositOrderWriteOffRequest: Encodable, Equatable {
let orderNumber: String
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
}
}
/// 退
struct DepositOrderRefundRequest: Encodable, Equatable {
let orderNumber: String
let refundReason: String
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case refundReason = "refund_reason"
}
}
/// 退退退
enum OrderRefundMode: Int, CaseIterable, Identifiable, Hashable {
case full = 1
case partial = 3
var id: Int { rawValue }
/// 退
var title: String {
switch self {
case .full:
return "全额退款"
case .partial:
return "部分退款"
}
}
}
/// 退
struct OrderRefundRequest: Encodable, Equatable {
let orderNumber: String
let refundType: Int
let refundAmount: String
let refundReason: String
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case refundType = "refund_type"
case refundAmount = "refund_amount"
case refundReason = "refund_reason"
}
}
/// push
enum OrdersRoute: Hashable {
case storeDetail(OrderEntity)
case writeOffDetail(WriteOffOrderItem)
case depositDetail(orderNumber: String)
case depositShootingInfo(orderNumber: String, scenicSpotId: Int, photogUid: Int)
case historicalShooting(orderNumber: String)
case multiTravelTaskUpload(orderNumber: String)
case orderTrailer(orderNumber: String, title: String)
}
///
struct StoreOrderDetailResponse: Decodable, Equatable, Hashable {
let orderNumber: String
let orderType: Int
let orderTypeLabel: String
let createdAt: String
let orderStatus: Int
let orderStatusName: String
let actualPayAmount: String
let actualRefundAmount: String
let payTypeName: String
let payTime: String
let completeTime: String
let userId: Int
let phone: String
let projectId: Int
let projectName: String
let remark: String
let multiTravel: StoreOrderMultiTravelInfo?
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case orderType = "order_type"
case orderTypeLabel = "order_type_label"
case createdAt = "created_at"
case orderStatus = "order_status"
case orderStatusName = "order_status_name"
case actualPayAmount = "actual_pay_amount"
case actualRefundAmount = "actual_refund_amount"
case payTypeName = "pay_type_name"
case payTime = "pay_time"
case completeTime = "complete_time"
case userId = "user_id"
case phone
case projectId = "project_id"
case projectName = "project_name"
case remark
case multiTravel = "multi_travel"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
orderType = try container.decodeLossyInt(forKey: .orderType) ?? 0
orderTypeLabel = try container.decodeLossyString(forKey: .orderTypeLabel)
createdAt = try container.decodeLossyString(forKey: .createdAt)
orderStatus = try container.decodeLossyInt(forKey: .orderStatus) ?? 0
orderStatusName = try container.decodeLossyString(forKey: .orderStatusName)
actualPayAmount = try container.decodeLossyString(forKey: .actualPayAmount)
actualRefundAmount = try container.decodeLossyString(forKey: .actualRefundAmount)
payTypeName = try container.decodeLossyString(forKey: .payTypeName)
payTime = try container.decodeLossyString(forKey: .payTime)
completeTime = try container.decodeLossyString(forKey: .completeTime)
userId = try container.decodeLossyInt(forKey: .userId) ?? 0
phone = try container.decodeLossyString(forKey: .phone)
projectId = try container.decodeLossyInt(forKey: .projectId) ?? 0
projectName = try container.decodeLossyString(forKey: .projectName)
remark = try container.decodeLossyString(forKey: .remark)
multiTravel = try container.decodeIfPresent(StoreOrderMultiTravelInfo.self, forKey: .multiTravel)
}
}
///
struct StoreOrderMultiTravelInfo: Decodable, Equatable, Hashable {
let projectInfo: StoreOrderProjectInfo?
let shootingList: [StoreOrderShootingListItem]
enum CodingKeys: String, CodingKey {
case projectInfo = "project_info"
case shootingList = "shooting_list"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
projectInfo = try container.decodeIfPresent(StoreOrderProjectInfo.self, forKey: .projectInfo)
shootingList = try container.decodeIfPresent([StoreOrderShootingListItem].self, forKey: .shootingList) ?? []
}
}
///
struct StoreOrderProjectInfo: Decodable, Equatable, Hashable {
let settleSpotNum: Int
let singleSpotMaterialNum: Int
let singleSpotPhotoNum: Int
let singleSpotVideoNum: Int
enum CodingKeys: String, CodingKey {
case settleSpotNum = "settle_spot_num"
case singleSpotMaterialNum = "single_spot_material_num"
case singleSpotPhotoNum = "single_spot_photo_num"
case singleSpotVideoNum = "single_spot_video_num"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
settleSpotNum = try container.decodeLossyInt(forKey: .settleSpotNum) ?? 0
singleSpotMaterialNum = try container.decodeLossyInt(forKey: .singleSpotMaterialNum) ?? 0
singleSpotPhotoNum = try container.decodeLossyInt(forKey: .singleSpotPhotoNum) ?? 0
singleSpotVideoNum = try container.decodeLossyInt(forKey: .singleSpotVideoNum) ?? 0
}
}
///
struct StoreOrderShootingListItem: Decodable, Identifiable, Equatable, Hashable {
var id: String { "\(scenicSpotId)-\(photogUid)" }
let scenicSpotId: Int
let photogUid: Int
let scenicSpotName: String
let staffName: String
let photogName: String
let status: Int
let startAvg: Double
let start: Double
enum CodingKeys: String, CodingKey {
case scenicSpotId = "scenic_spot_id"
case photogUid = "photog_uid"
case scenicSpotName = "scenic_spot_name"
case staffName = "staff_name"
case photogName = "photog_name"
case status
case startAvg = "start_avg"
case start
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
scenicSpotId = try container.decodeLossyInt(forKey: .scenicSpotId) ?? 0
photogUid = try container.decodeLossyInt(forKey: .photogUid) ?? 0
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
staffName = try container.decodeLossyString(forKey: .staffName)
photogName = try container.decodeLossyString(forKey: .photogName)
status = try container.decodeLossyInt(forKey: .status) ?? 0
startAvg = try container.decodeLossyDouble(forKey: .startAvg) ?? 0
start = try container.decodeLossyDouble(forKey: .start) ?? 0
}
}
///
struct StoreOrderShootingDetailResponse: Decodable, Equatable, Hashable {
let scenicSpotId: Int
let scenicSpotName: String
let orderType: Int
let orderTypeName: String
let orderComment: StoreOrderComment?
let materialList: [OrderMediaFile]
let completeList: [OrderMediaFile]
enum CodingKeys: String, CodingKey {
case scenicSpotId = "scenic_spot_id"
case scenicSpotName = "scenic_spot_name"
case orderType = "order_type"
case orderTypeName = "order_type_name"
case orderComment = "order_comment"
case materialList = "material_list"
case completeList = "complete_list"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
scenicSpotId = try container.decodeLossyInt(forKey: .scenicSpotId) ?? 0
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
orderType = try container.decodeLossyInt(forKey: .orderType) ?? 0
orderTypeName = try container.decodeLossyString(forKey: .orderTypeName)
orderComment = try container.decodeIfPresent(StoreOrderComment.self, forKey: .orderComment)
materialList = try container.decodeIfPresent([OrderMediaFile].self, forKey: .materialList) ?? []
completeList = try container.decodeIfPresent([OrderMediaFile].self, forKey: .completeList) ?? []
}
}
///
struct StoreOrderComment: Decodable, Equatable, Hashable {
let starShooting: Int
let starRetouching: Int
let starScenery: Int
let starService: Int
let starCamera: Int
let content: String
let createdAt: String
enum CodingKeys: String, CodingKey {
case starShooting = "star_shooting"
case starRetouching = "star_retouching"
case starScenery = "star_scenery"
case starService = "star_service"
case starCamera = "star_camera"
case content
case createdAt = "created_at"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
starShooting = try container.decodeLossyInt(forKey: .starShooting) ?? 0
starRetouching = try container.decodeLossyInt(forKey: .starRetouching) ?? 0
starScenery = try container.decodeLossyInt(forKey: .starScenery) ?? 0
starService = try container.decodeLossyInt(forKey: .starService) ?? 0
starCamera = try container.decodeLossyInt(forKey: .starCamera) ?? 0
content = try container.decodeLossyString(forKey: .content)
createdAt = try container.decodeLossyString(forKey: .createdAt)
}
}
///
struct OrderMediaFile: Decodable, Identifiable, Equatable, Hashable {
var id: String { "\(fileUrl)-\(fileName)-\(uploadTime)" }
let fileName: String
let fileUrl: String
let fileType: Int
let fileSize: Int64
let coverUrl: String
let uploadTime: String
var isVideo: Bool {
fileType == 1
}
var previewURLString: String {
coverUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? fileUrl : coverUrl
}
enum CodingKeys: String, CodingKey {
case fileName = "file_name"
case fileUrl = "file_url"
case fileType = "file_type"
case fileSize = "file_size"
case coverUrl = "cover_url"
case uploadTime = "upload_time"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
fileName = try container.decodeLossyString(forKey: .fileName)
fileUrl = try container.decodeLossyString(forKey: .fileUrl)
fileType = try container.decodeLossyInt(forKey: .fileType) ?? 0
fileSize = Int64(try container.decodeLossyInt(forKey: .fileSize) ?? 0)
coverUrl = try container.decodeLossyString(forKey: .coverUrl)
uploadTime = try container.decodeLossyString(forKey: .uploadTime)
}
}
///
struct MultiTravelShootHistoryResponse: Decodable, Equatable, Hashable {
let projectName: String
let projectType: Int
let projectTypeName: String
let photogSpotList: [PhotogSpotItem]
enum CodingKeys: String, CodingKey {
case projectName = "project_name"
case projectType = "project_type"
case projectTypeName = "project_type_name"
case photogSpotList = "photog_spot_list"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
projectName = try container.decodeLossyString(forKey: .projectName)
projectType = try container.decodeLossyInt(forKey: .projectType) ?? 0
projectTypeName = try container.decodeLossyString(forKey: .projectTypeName)
photogSpotList = try container.decodeIfPresent([PhotogSpotItem].self, forKey: .photogSpotList) ?? []
}
}
///
struct PhotogSpotItem: Decodable, Identifiable, Equatable, Hashable {
var id: String { "\(scenicSpotId)-\(photogUid)" }
let scenicSpotId: Int
let photogUid: Int
let scenicSpotName: String
let photogNickname: String
let photogName: String
let files: [OrderMediaFile]
var photographerDisplayName: String {
let nickname = photogNickname.trimmingCharacters(in: .whitespacesAndNewlines)
return nickname.isEmpty ? photogName : nickname
}
enum CodingKeys: String, CodingKey {
case scenicSpotId = "scenic_spot_id"
case photogUid = "photog_uid"
case scenicSpotName = "scenic_spot_name"
case photogNickname = "photog_nickname"
case photogName = "photog_name"
case files
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
scenicSpotId = try container.decodeLossyInt(forKey: .scenicSpotId) ?? 0
photogUid = try container.decodeLossyInt(forKey: .photogUid) ?? 0
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
photogNickname = try container.decodeLossyString(forKey: .photogNickname)
photogName = try container.decodeLossyString(forKey: .photogName)
files = try container.decodeIfPresent([OrderMediaFile].self, forKey: .files) ?? []
}
}
///
struct MultiTravelVerifiedScenicSpotItem: Decodable, Identifiable, Equatable, Hashable {
let id: Int
let name: String
enum CodingKeys: String, CodingKey {
case id
case name
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeLossyInt(forKey: .id) ?? 0
name = try container.decodeLossyString(forKey: .name)
}
}
///
struct MultiTravelUploadMaterialRequest: Encodable, Equatable {
let orderNumber: String
let scenicSpotId: Int
let cloudFile: [MultiTravelCloudFileItem]
let uploadFile: [MultiTravelUploadFileItem]
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case scenicSpotId = "scenic_spot_id"
case cloudFile = "cloud_file"
case uploadFile = "upload_file"
}
}
///
struct MultiTravelCloudFileItem: Encodable, Equatable {
let fileId: Int
enum CodingKeys: String, CodingKey {
case fileId = "file_id"
}
}
///
struct MultiTravelUploadFileItem: Encodable, Equatable {
let fileName: String
let fileUrl: String
enum CodingKeys: String, CodingKey {
case fileName = "file_name"
case fileUrl = "file_url"
}
}
///
struct StoreOrderDetailDisplay: Equatable {
let orderNumber: String
let orderTypeLabel: String
let createdAt: String
let orderStatusName: String
let actualPayAmount: String
let actualRefundAmount: String
let payTypeName: String
let payTime: String
let completeTime: String
let userId: Int
let phone: String
let projectId: Int
let projectName: String
let remark: String
/// 使
init(item: OrderEntity) {
orderNumber = item.orderNumber
orderTypeLabel = item.orderTypeLabel
createdAt = item.createdAt
orderStatusName = item.orderStatusName
actualPayAmount = item.actualPayAmount
actualRefundAmount = item.actualRefundAmount.isEmpty ? item.refundAmount : item.actualRefundAmount
payTypeName = item.payTypeName
payTime = item.payTime
completeTime = item.completeTime
userId = item.userId
phone = item.phone
projectId = item.projectId
projectName = item.projectName
remark = item.remark
}
/// 使
init(detail: StoreOrderDetailResponse) {
orderNumber = detail.orderNumber
orderTypeLabel = detail.orderTypeLabel
createdAt = detail.createdAt
orderStatusName = detail.orderStatusName
actualPayAmount = detail.actualPayAmount
actualRefundAmount = detail.actualRefundAmount
payTypeName = detail.payTypeName
payTime = detail.payTime
completeTime = detail.completeTime
userId = detail.userId
phone = detail.phone
projectId = detail.projectId
projectName = detail.projectName
remark = detail.remark
}
}
///
enum OrderNumberParser {
/// URL
static func parse(_ raw: String) -> String? {
let text = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty else { return nil }
if let url = URL(string: text),
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems,
let value = queryItems.first(where: { $0.name.lowercased() == "order_number" })?.value,
!value.isEmpty {
return value
}
let patterns = [
"order[_-]?number[=:]([A-Za-z0-9_-]+)",
"([A-Za-z0-9_-]{8,})"
]
for pattern in patterns {
if let match = firstCapture(in: text, pattern: pattern) {
return match
}
}
if !text.contains(" "), text.count >= 6 {
return text
}
return nil
}
///
private static func firstCapture(in text: String, pattern: String) -> String? {
guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else {
return nil
}
let range = NSRange(text.startIndex..<text.endIndex, in: text)
guard let match = regex.firstMatch(in: text, options: [], range: range),
match.numberOfRanges > 1,
let captureRange = Range(match.range(at: 1), in: text) else {
return nil
}
return String(text[captureRange])
}
}
private extension KeyedDecodingContainer {
/// JSON
func decodeLossyString(forKey key: Key) throws -> String {
if let value = try? decodeIfPresent(String.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return String(value)
}
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return String(value)
}
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value ? "1" : "0"
}
return ""
}
/// IntDoubleBool
func decodeLossyInt(forKey key: Key) throws -> Int? {
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
if let intValue = Int(text) {
return intValue
}
if let doubleValue = Double(text) {
return Int(doubleValue)
}
}
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return Int(value)
}
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value ? 1 : 0
}
return nil
}
/// BoolInt
func decodeLossyBool(forKey key: Key) throws -> Bool? {
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return value != 0
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
switch value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
case "1", "true", "yes":
return true
case "0", "false", "no":
return false
default:
return nil
}
}
return nil
}
/// DoubleInt
func decodeLossyDouble(forKey key: Key) throws -> Double? {
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return Double(value)
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
return Double(value.trimmingCharacters(in: .whitespacesAndNewlines))
}
return nil
}
}