You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift JSON解析问题:空数组替代nil导致崩溃的解决方法

解决Swift Codable解析空数组为可选对象nil的问题

针对API返回空数组替代nil,但模型定义为单个可选对象导致的解析崩溃问题,提供三种实用解决方案:

方案一:模型内自定义解码逻辑

直接在目标模型中重写init(from decoder: Decoder),针对每个字段单独处理两种情况(单个对象/空数组):

struct FinancialData: Codable {
    let ebitda: Ebitda?
    let quickRatio: QuickRatio?
    
    enum CodingKeys: String, CodingKey {
        case ebitda, quickRatio
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        // 处理ebitda字段:优先解码为单个对象,失败则检查是否为空数组
        if let ebitdaObj = try? container.decode(Ebitda.self, forKey: .ebitda) {
            ebitda = ebitdaObj
        } else if let emptyArray = try? container.decode([Ebitda].self, forKey: .ebitda), emptyArray.isEmpty {
            ebitda = nil
        } else {
            // 非预期格式(如非空数组),可设为nil或抛出错误,根据业务需求调整
            ebitda = nil
        }
        
        // 同理处理quickRatio字段
        if let ratioObj = try? container.decode(QuickRatio.self, forKey: .quickRatio) {
            quickRatio = ratioObj
        } else if let emptyArray = try? container.decode([QuickRatio].self, forKey: .quickRatio), emptyArray.isEmpty {
            quickRatio = nil
        } else {
            quickRatio = nil
        }
    }
}

// 子模型保持不变
struct Ebitda: Codable {
    let value: Double
    let period: String
}

struct QuickRatio: Codable {
    let value: Double
    let period: String
}

优缺点:无需额外定义类型,但字段较多时代码重复度高,仅适合少量字段的场景。

方案二:通用包装类型

定义一个实现Codable的通用结构体,统一处理「单个对象/空数组」的解码逻辑,复用性更强:

// 通用包装类型:支持将单个对象解码为T?,空数组解码为nil
struct OptionalSingleOrEmptyArray<T: Codable>: Codable {
    let value: T?
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            // 尝试解码为单个对象
            value = try container.decode(T.self)
        } catch {
            // 解码失败则尝试解析数组
            let array = try container.decode([T].self)
            // 空数组返回nil;若API可能返回非空数组,可改为取第一个元素或抛出错误
            value = array.isEmpty ? nil : array.first
        }
    }
    
    // 编码时反向处理:nil转为空数组,非nil转为单个对象
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        if let value = value {
            try container.encode(value)
        } else {
            try container.encode([])
        }
    }
}

// 模型中使用包装类型,可加计算属性简化外部访问
struct FinancialData: Codable {
    let ebitda: OptionalSingleOrEmptyArray<Ebitda>
    let quickRatio: OptionalSingleOrEmptyArray<QuickRatio>
    
    // 对外暴露原生可选类型的计算属性
    var ebitdaValue: Ebitda? { ebitda.value }
    var quickRatioValue: QuickRatio? { quickRatio.value }
}

优缺点:一次定义多处复用,适合多字段场景,但需通过value属性访问实际对象,稍显繁琐。

方案三:属性包装器(推荐)

使用Swift属性包装器,实现最简洁的语法,对外完全兼容原生可选类型的用法:

@propertyWrapper
struct SingleOrEmptyArray<T: Codable>: Codable {
    var wrappedValue: T?
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            wrappedValue = try container.decode(T.self)
        } catch {
            let array = try container.decode([T].self)
            wrappedValue = array.isEmpty ? nil : array.first
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        if let value = wrappedValue {
            try container.encode(value)
        } else {
            try container.encode([])
        }
    }
}

// 让属性包装器支持Equatable(可选,根据模型需求添加)
extension SingleOrEmptyArray: Equatable where T: Equatable {}

// 模型中直接使用属性包装器,用法与原生可选类型一致
struct FinancialData: Codable {
    @SingleOrEmptyArray var ebitda: Ebitda?
    @SingleOrEmptyArray var quickRatio: QuickRatio?
}

使用时直接访问ebitda即可,完全无需关心底层的解码逻辑,语法最简洁,复用性最强,是优先推荐的方案。


内容的提问来源于stack exchange,提问作者Micsa Dan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 06:35:16