English | 中文
SmartCodable enhances Apple's native Codable with production-ready resilience. When standard Codable fails on a single missing field or type mismatch, your entire model is lost. SmartCodable gracefully recovers — falling back to defaults, converting types automatically, and never interrupting the parse.
| Scenario | Standard Codable | SmartCodable |
|---|---|---|
| Missing key | ❌ Throws keyNotFound, entire model fails |
✅ Uses property initializer as default |
Type mismatch (e.g., "123" for Int) |
❌ Throws typeMismatch, entire model fails |
✅ Auto-converts, returns 123 |
| Null value for non-optional | ❌ Throws valueNotFound, entire model fails |
✅ Falls back to default value |
| Extra unknown keys | ✅ Ignored | ✅ Ignored |
vs HandyJSON: SmartCodable builds on Apple's Codable protocol — no unsafe runtime reflection, no ABI stability risks. HandyJSON relies on Swift metadata reflection that may break across Swift versions.
vs Manual init(from:): SmartCodable eliminates the boilerplate of writing decodeIfPresent + ?? for every property. Same safety, zero ceremony.
import SmartCodable
struct User: SmartCodableX {
var name: String = ""
var age: Int = 0
}
// ✅ Normal case
let user = User.deserialize(from: ["name": "John", "age": 30])
// User(name: "John", age: 30)
// ✅ Missing field — falls back to default
let user2 = User.deserialize(from: ["name": "John"])
// User(name: "John", age: 0)
// ✅ Type mismatch — auto-converts
let user3 = User.deserialize(from: ["name": "John", "age": "30"])
// User(name: "John", age: 30)To conform to SmartCodable, a class needs to implement an empty initializer:
class BasicTypes: SmartCodableX {
var int: Int = 2
var doubleOptional: Double?
required init() {}
}
let model = BasicTypes.deserialize(from: json)For struct, the compiler provides a default empty initializer:
struct BasicTypes: SmartCodableX {
var int: Int = 2
var doubleOptional: Double?
}
let model = BasicTypes.deserialize(from: json)dependencies: [
.package(url: "https://github.com/iAmMccc/SmartCodable.git", branch: "main")
]SmartCodableprovides the core parsing capabilities with no external dependencies.- For class inheritance via
@SmartSubclass, see the companion package SmartCodableMacro.
Starting from 7.0, SmartCodable no longer ships via CocoaPods. If you must stay on CocoaPods, please use the 6.x series, which is preserved on the 6.1.0 branch:
pod 'SmartCodable', '~> 6.1'The 6.x line will receive critical fixes only. All new features will land in 7.x and beyond — we strongly recommend migrating to Swift Package Manager.
Only types conforming to SmartCodable (or [SmartCodable] for arrays) can use these methods:
public static func deserialize(from dict: [String: Any]?, designatedPath: String? = nil, options: Set<SmartDecodingOption>? = nil) -> Self?
public static func deserialize(from json: String?, designatedPath: String? = nil, options: Set<SmartDecodingOption>? = nil) -> Self?
public static func deserialize(from data: Data?, designatedPath: String? = nil, options: Set<SmartDecodingOption>? = nil) -> Self?
public static func deserializePlist(from data: Data?, designatedPath: String? = nil, options: Set<SmartDecodingOption>? = nil) -> Self?Multi-Format Input Support:
| Input Type | Example Usage | Internal Conversion |
|---|---|---|
| Dictionary | Model.deserialize(from: dict) |
Directly processes native collections |
| Array | [Model].deserialize(from: arr) |
Directly processes native collections |
| JSON String | Model.deserialize(from: jsonString) |
Converts to Data via UTF-8 |
| Data | Model.deserialize(from: data) |
Processes directly |
Deep Path Navigation — Extract nested data directly:
// JSON: {"data": {"user": {"info": { ... }}}}
let model = Model.deserialize(from: json, designatedPath: "data.user.info")Decoding Strategies:
let options: Set<SmartDecodingOption> = [
.key(.convertFromSnakeCase),
.date(.iso8601),
.data(.base64)
]
let model = Model.deserialize(from: json, options: options)| Strategy Type | Available Options | Description |
|---|---|---|
| Key Decoding | .fromSnakeCase |
snake_case → camelCase |
.firstLetterLower |
"FirstName" → "firstName" | |
.firstLetterUpper |
"firstName" → "FirstName" | |
| Date Decoding | .iso8601, .secondsSince1970, etc. |
Full Codable date strategies |
| Data Decoding | .base64 |
Binary data processing |
| Float Decoding | .convertToString, .throw |
NaN/∞ handling |
⚠️ Important: Only one strategy per type is allowed (last one wins if duplicates exist)
Map JSON keys to Swift property names. First non-null match wins:
static func mappingForKey() -> [SmartKeyTransformer]? {
[
CodingKeys.id <--- ["user_id", "userId", "id"],
CodingKeys.name <--- "nested.path.to.name" // nested path supported
]
}Convert between JSON values and custom types:
static func mappingForValue() -> [SmartValueTransformer]? {
[
CodingKeys.url <--- SmartURLTransformer(prefix: "https://"),
CodingKeys.date <--- SmartDateFormatTransformer(DateFormatter()),
CodingKeys.status <--- FastTransformer<Status, String>(
fromJSON: { Status(rawValue: $0 ?? "") },
toJSON: { $0?.rawValue }
),
]
}Built-in Transformers:
| Transformer | JSON → Object |
|---|---|
SmartDateTransformer |
Double/String → Date |
SmartDateFormatTransformer |
String (custom format) → Date |
SmartDataTransformer |
Base64 String → Data |
SmartURLTransformer |
String → URL (with optional prefix & encoding) |
SmartHexColorTransformer |
Hex String → UIColor/NSColor |
Need custom logic? Implement ValueTransformable:
public protocol ValueTransformable {
associatedtype Object
associatedtype JSON
func transformFromJSON(_ value: Any?) -> Object?
func transformToJSON(_ value: Object?) -> JSON?
}| Wrapper | Purpose | Example |
|---|---|---|
@SmartAny |
Any, [Any], [String: Any] support |
@SmartAny var dict: [String: Any] = [:] |
@SmartIgnored |
Skip property during decoding | @SmartIgnored var cache: String = "" |
@SmartFlat |
Flatten nested object into parent | @SmartFlat var profile: Profile? |
@SmartPublished |
Combine ObservableObject support |
@SmartPublished var name: String? |
@SmartHexColor |
Hex string → UIColor/NSColor | @SmartHexColor var color: UIColor? |
@SmartDate |
Multi-format date parsing | @SmartDate var date: Date? |
@SmartCompact.Array |
Skip invalid array elements | @SmartCompact.Array var ids: [Int] |
@SmartCompact.Dictionary |
Skip invalid dict entries | @SmartCompact.Dictionary var info: [String: String] |
@SmartAny example — support Any types that Codable can't handle natively:
struct Model: SmartCodableX {
@SmartAny var dict: [String: Any] = [:]
@SmartAny var arr: [Any] = []
@SmartAny var any: Any?
}
let dict: [String: Any] = [
"dict": ["name": "Lisa"],
"arr": [1, 2, 3],
"any": "Mccc"
]
let model = Model.deserialize(from: dict)
// Model(dict: ["name": "Lisa"], arr: [1, 2, 3], any: "Mccc")@SmartIgnored example — skip property during decoding:
struct Model: SmartCodableX {
@SmartIgnored
var name: String = ""
}
let model = Model.deserialize(from: ["name": "Mccc"])
// Model(name: "") — "name" was ignored, keeps default@SmartFlat example — flatten nested fields into parent:
struct Model: SmartCodableX {
var name: String = ""
@SmartFlat var profile: Profile?
}
struct Profile: SmartCodableX {
var name: String = ""
var age: Int = 0
}
// JSON: {"name": "Mccc", "age": 18}
// profile gets name="Mccc", age=18 from the SAME level@SmartCompact.Array example — tolerant array parsing:
struct Model: Decodable {
@SmartCompact.Array var ages: [Int]
}
// JSON: {"ages": ["Tom", 1, {}, 2, 3, "4"]}
// Result: ages = [1, 2, 3, 4] (invalid elements skipped, "4" auto-converted)Class inheritance support has been moved to a separate package — SmartCodableMacro. It depends on swift-syntax, so we ship it independently to keep this core library lightweight and dependency-free.
Add it alongside SmartCodable when you need @SmartSubclass:
dependencies: [
.package(url: "https://github.com/iAmMccc/SmartCodableMacro.git", branch: "main")
]For inheritance usage on Swift versions prior to 5.9, see Inheritance in Lower Versions.
Simple enums — conform to SmartCaseDefaultable:
enum Sex: String, SmartCaseDefaultable {
case man
case woman
}Enums with associated values — conform to SmartAssociatedEnumerable and provide a transformer via mappingForValue():
struct Model: SmartCodableX {
var sex: Sex = .man
static func mappingForValue() -> [SmartValueTransformer]? {
[ CodingKeys.sex <--- SexTransformer() ]
}
}
enum Sex: SmartAssociatedEnumerable {
case man, woman, other(String)
}
struct SexTransformer: ValueTransformable {
typealias Object = Sex
typealias JSON = String
func transformFromJSON(_ value: Any?) -> Sex? {
guard let str = value as? String else { return nil }
switch str {
case "man": return .man
case "woman": return .woman
default: return .other(str)
}
}
func transformToJSON(_ value: Sex?) -> String? { nil }
}didFinishMapping() — runs after decoding completes:
struct Model: SmartCodableX {
var name: String = ""
mutating func didFinishMapping() {
name = "I am \(name)"
}
}SmartUpdater — update an existing model with new data:
var model = Model.deserialize(from: initialData)!
SmartUpdater.update(&model, from: newData)SmartCodable handles parsing failures gracefully, ensuring the entire model never fails:
let dict = ["number1": "123", "number2": "Mccc", "number3": "Mccc"]
struct Model: SmartCodableX {
var number1: Int?
var number2: Int?
var number3: Int = 1
}
// Result: Model(number1: 123, number2: nil, number3: 1)- Type conversion:
"123"(String) →123(Int) automatically - Default fill: When conversion fails, uses the property's initializer value (
number3 = 1) - Optional handling: When conversion fails for optionals, returns
nil(number2 = nil)
Performance tip for large data: When parsing very large datasets, avoid unnecessary compatibility overhead — use CodingKeys to exclude unused properties instead of @SmartIgnored, as it's more efficient.
SmartCodable auto-detects and parses string-encoded JSON:
struct Model: SmartCodableX {
var hobby: Hobby?
}
// JSON: {"hobby": "{\"name\":\"sleep\"}"}
// hobby is parsed as Hobby(name: "sleep"), not a raw stringSmartSentinel.debugMode = .verbose // .none | .verbose | .alert
SmartSentinel.onLogGenerated { log in print(log) }================================ [Smart Sentinel] ================================
UserModel 👈🏻 👀
╆━ UserModel
┆┄ age : Expected Int, got String — auto-converted
┆┄ email : Key not found — using default ""
====================================================================================
| 🔧 Migrate from HandyJSON | Step-by-step migration guide |
| 🛠 SmartModeler | JSON → SmartCodable model generator |
| 👀 SmartSentinel | Real-time parsing log viewer |
| 💖 Contributing | Support SmartCodable development |
| 🏆 Contributors | Key contributors |
SmartCodable is an open-source project dedicated to making Swift data parsing more robust, flexible and efficient. We welcome all developers to join our community!

