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

Swift(UIKit)中如何优雅过滤API返回JSON的空值与Null值?

Better Ways to Filter Empty Strings and Nulls in Swift Decodable for Your Cat API Response

Great question! Your current implementation gets the job done, but all that repetitive code for handling characteristic1 through characteristic5 feels unnecessary. Let’s make this more elegant and scalable while keeping all your required fields intact for the UI layer.

1. Extend KeyedDecodingContainer for Reusable Logic

The cleanest fix is to create a custom extension method that handles both null values (via decodeIfPresent) and empty strings in one go. This eliminates the need for your checkEmpty method and reduces repetition in the initializer.

extension KeyedDecodingContainer {
    /// Decodes a string, returning nil if the value is null OR an empty string
    func decodeNonEmptyStringIfPresent(forKey key: Key) throws -> String? {
        guard let stringValue = try decodeIfPresent(String.self, forKey: key) else {
            return nil
        }
        return stringValue.isEmpty ? nil : stringValue
    }
}

Now update your Cat struct’s initializer to use this method (note: I fixed a mapping bug between your JSON's "breedname" and struct's breedName):

struct Cat: Decodable {
    let breedName: String
    let picture: String
    let characteristic1: String?
    let characteristic2: String?
    let characteristic3: String?
    let characteristic4: String?
    let characteristic5: String?

    enum CodingKeys: String, CodingKey {
        case breedName = "breedname"
        case picture
        case characteristic1, characteristic2, characteristic3, characteristic4, characteristic5
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.breedName = try container.decode(String.self, forKey: .breedName)
        self.picture = try container.decode(String.self, forKey: .picture)
        
        // Now each characteristic line is a single, clean call
        self.characteristic1 = try container.decodeNonEmptyStringIfPresent(forKey: .characteristic1)
        self.characteristic2 = try container.decodeNonEmptyStringIfPresent(forKey: .characteristic2)
        self.characteristic3 = try container.decodeNonEmptyStringIfPresent(forKey: .characteristic3)
        self.characteristic4 = try container.decodeNonEmptyStringIfPresent(forKey: .characteristic4)
        self.characteristic5 = try container.decodeNonEmptyStringIfPresent(forKey: .characteristic5)
    }
}

2. Use a Custom Wrapper Type (For Cross-Struct Reusability)

If you find yourself needing this non-empty string logic across multiple structs in your codebase, a custom wrapper type is a great way to encapsulate the behavior centrally.

/// A wrapper that converts null values AND empty strings to nil
struct NonEmptyString: Decodable {
    let value: String?
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        
        do {
            let stringValue = try container.decode(String.self)
            self.value = stringValue.isEmpty ? nil : stringValue
        } catch DecodingError.valueNotFound {
            // Handle null values gracefully
            self.value = nil
        }
    }
}

Then integrate it into your Cat struct:

struct Cat: Decodable {
    let breedName: String
    let picture: String
    let characteristic1: String?
    let characteristic2: String?
    let characteristic3: String?
    let characteristic4: String?
    let characteristic5: String?

    enum CodingKeys: String, CodingKey { /* Same as before */ }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.breedName = try container.decode(String.self, forKey: .breedName)
        self.picture = try container.decode(String.self, forKey: .picture)
        
        self.characteristic1 = try container.decode(NonEmptyString.self, forKey: .characteristic1).value
        self.characteristic2 = try container.decode(NonEmptyString.self, forKey: .characteristic2).value
        self.characteristic3 = try container.decode(NonEmptyString.self, forKey: .characteristic3).value
        self.characteristic4 = try container.decode(NonEmptyString.self, forKey: .characteristic4).value
        self.characteristic5 = try container.decode(NonEmptyString.self, forKey: .characteristic5).value
    }
}

This approach makes it easy to update the logic later (like trimming whitespace before checking for emptiness) without changing every struct that uses it.

3. Bonus: If UI Can Adapt, Use an Array (Optional)

While you mentioned the UI needs to reference fixed individual fields, if you ever get the green light to adjust the UI, you could map the 5 characteristics into an array of non-empty strings. This cuts down on repetition even further:

struct Cat: Decodable {
    let breedName: String
    let picture: String
    let characteristics: [String] // Only contains non-empty, non-null values

    enum CodingKeys: String, CodingKey { /* Same as before */ }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.breedName = try container.decode(String.self, forKey: .breedName)
        self.picture = try container.decode(String.self, forKey: .picture)
        
        // Collect and filter all characteristics in one go
        let allCharacteristics = [
            try container.decodeNonEmptyStringIfPresent(forKey: .characteristic1),
            try container.decodeNonEmptyStringIfPresent(forKey: .characteristic2),
            try container.decodeNonEmptyStringIfPresent(forKey: .characteristic3),
            try container.decodeNonEmptyStringIfPresent(forKey: .characteristic4),
            try container.decodeNonEmptyStringIfPresent(forKey: .characteristic5)
        ]
        
        self.characteristics = allCharacteristics.compactMap { $0 }
    }
}

This works best if your UI can display a dynamic list instead of fixed labels for each characteristic.

Final Recommendation

For your current use case (fixed fields required by the UI), the first approach (extending KeyedDecodingContainer) is the best balance of simplicity and cleanliness. It cuts down on repetitive code without introducing extra complexity, and it’s easy to maintain if you add more characteristic fields later.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 19:12:47