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

JSONDecoder解析含嵌套JSON字符串的JSON报错,求Decodable扩展方案

Solution for Valid JSON (Values are Strings or JSON Objects)

If your JSON is valid (no unescaped newlines in strings) and values can be either plain strings or JSON objects/arrays, you can create a custom Decodable wrapper type to handle both cases. This avoids using Coding Keys and works for any dictionary structure.

Step 1: Create the Wrapper Types

import Foundation

// Wrapper that can decode either a String or a JSON value (object/array) and convert it to a String
struct StringOrJSON: Decodable {
    let value: String
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        
        // First attempt to decode as a plain String
        do {
            value = try container.decode(String.self)
        } catch {
            // If that fails, decode as any JSON value and convert it back to a JSON string
            let jsonValue = try container.decode(JSONValue.self)
            let jsonData = try JSONSerialization.data(withJSONObject: jsonValue.rawValue, options: [])
            guard let jsonString = String(data: jsonData, encoding: .utf8) else {
                throw DecodingError.dataCorruptedError(
                    in: container,
                    debugDescription: "Failed to convert JSON value to string"
                )
            }
            value = jsonString
        }
    }
}

// Helper type to decode any JSON value (object, array, string, number, bool, null)
enum JSONValue: Decodable {
    case object([String: JSONValue])
    case array([JSONValue])
    case string(String)
    case number(Double)
    case bool(Bool)
    case null
    
    var rawValue: Any {
        switch self {
        case .object(let dict): return dict.mapValues { $0.rawValue }
        case .array(let array): return array.map { $0.rawValue }
        case .string(let str): return str
        case .number(let num): return num
        case .bool(let b): return b
        case .null: return NSNull()
        }
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        
        if let object = try? container.decode([String: JSONValue].self) {
            self = .object(object)
        } else if let array = try? container.decode([JSONValue].self) {
            self = .array(array)
        } else if let string = try? container.decode(String.self) {
            self = .string(string)
        } else if let number = try? container.decode(Double.self) {
            self = .number(number)
        } else if let bool = try? container.decode(Bool.self) {
            self = .bool(bool)
        } else if container.decodeNil() {
            self = .null
        } else {
            throw DecodingError.dataCorruptedError(
                in: container,
                debugDescription: "Unsupported JSON value type"
            )
        }
    }
}

Step 2: Decode and Convert to [String: String]

let jsonData = """
{
    "plainKey": "plain string",
    "jsonKey": {
        "key1": "value1",
        "key2": "value2"
    }
}
""".data(using: .utf8)!

do {
    let decoder = JSONDecoder()
    // Decode into [String: StringOrJSON] first
    let intermediateResult = try decoder.decode([String: StringOrJSON].self, from: jsonData)
    // Convert to [String: String]
    let finalResult = intermediateResult.mapValues { $0.value }
    
    print(finalResult["plainKey"]) // Optional("plain string")
    print(finalResult["jsonKey"])  // Optional("{\"key1\":\"value1\",\"key2\":\"value2\"}")
} catch {
    print("Decoding error: \(error)")
}

Solution for Invalid JSON (Unescaped Newlines in String Values)

If your JSON is invalid because string values contain unescaped newlines (like your example), you need to preprocess the data to fix these issues before decoding. Here's a simple preprocessor to escape newlines inside strings:

Step 1: Preprocess the Invalid JSON

func fixUnescapedNewlines(in jsonString: String) -> String {
    var fixedString = ""
    var insideString = false
    var escaped = false
    
    for char in jsonString {
        switch char {
        case "\"":
            if !escaped {
                insideString.toggle()
            }
            fixedString.append(char)
            escaped = false
        case "\\":
            fixedString.append(char)
            escaped = !escaped
        case "\n", "\r":
            if insideString && !escaped {
                // Escape newline characters inside strings
                fixedString.append(char == "\n" ? "\\n" : "\\r")
            } else {
                fixedString.append(char)
            }
            escaped = false
        default:
            fixedString.append(char)
            escaped = false
        }
    }
    return fixedString
}

Step 2: Use the Preprocessor Before Decoding

let invalidJsonString = """
{
    "someKey":"{
        \"key1\":\"value1\",
        \"key2\":\"value2\"
    }"
}
"""

// Fix the invalid JSON
let fixedJsonString = fixUnescapedNewlines(in: invalidJsonString)
guard let jsonData = fixedJsonString.data(using: .utf8) else {
    fatalError("Failed to convert fixed string to data")
}

// Now decode normally as [String: String]
do {
    let result = try JSONDecoder().decode([String: String].self, from: jsonData)
    print(result["someKey"]) // Optional("{\\n        \"key1\":\"value1\",\\n        \"key2\":\"value2\"\\n    }")
} catch {
    print("Decoding error: \(error)")
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 09:45:33