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
相关产品推荐
相关产品推荐

