GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - ExCodable/ExCodable: JSON-Model Conversion with Swift Codable

BranchesTags
Open more actions menu

Latest commit

 

History

135 Commits
135 Commits

Folders and files

NameName
Last commit message
Last commit date
.github/workflows
.github/workflows
 
 
Sources/ExCodable
Sources/ExCodable
 
 
Tests/ExCodableTests
Tests/ExCodableTests
 
 
.gitignore
.gitignore
 
 
ExCodable.podspec
ExCodable.podspec
 
 
LICENSE
LICENSE
 
 
Package.swift
Package.swift
 
 
README.md
README.md
 
 

Repository files navigation

ExCodable

Swift 5.10 Swift Package Manager Platforms
Build and Test GitHub Releases (latest SemVer) Deploy to CocoaPods Cocoapods
LICENSE GitHub stars @minglq

En | 中文

What's New in ExCodable 1.0

  • Uses @propertyWrapper instead of key-mapping, the new syntax is more concise, elegant and easier to use.
  • Optimized type conversions.
  • Fix bugs.

Documentations

  • Features
  • Usage
  • Requirements
  • Installation
  • Migration

Features

struct TestExCodable: ExAutoCodable {
    @ExCodable private(set)
    var int: Int = 0
    @ExCodable("nested.nested.string", "string", "str", "s") private(set)
    var string: String? = nil
}
  • Converts between JSON and models.
  • Based on and Extends Swift Codable.
  • Associates JSON keys to properties via annotations - @propertyWrapper:
    • ExCodable did not read/write memory via unsafe pointers.
    • No need to encode and decode properties one by one.
    • In most cases, the CodingKey types are no longer necessary, String literals are preferred.
    • Currently, requires declaring properties with var and provide default values.
  • Supports alternative keys for decoding.
  • Supports nested keys via String with dot syntax.
  • Supports builtin and custom type conversions, including nested optionals as well.
  • Supports manual encoding/decoding using subscripts.
  • Supports return nil or throw error when encoding/decoding failed.
  • Supports struct, enum, class and subclasses.
  • Supports JSON Data, String, Array and Dictionary.
  • Supports type inference.
  • Uses JSON encoder/decoder by default, supports PList and custom encoder/decoder.
  • Super lightweight.

TODO:

  • Supports let.
  • Supports var without default values.
  • Use Macros instead of protocol ExAutoCodable.

Usage

1. ExCodable

ExCodable requires declaring properties with var and provide default values.

struct TestStruct: ExAutoCodable {
    @ExCodable private(set)
    var int: Int = 0
    @ExCodable("string") private(set)
    var string: String? = nil
}

2. Alternative Keys

struct TestAlternativeKeys: ExAutoCodable {
    @ExCodable("string", "str", "s") private(set)
    var string: String! = nil
}

3. Nested Keys

struct TestNestedKeys: ExAutoCodable {
    @ExCodable("nested.nested.string") private(set)
    var string: String! = nil
}

4. enum

enum TestEnum: Int, Codable {
    case zero = 0, one = 1
}

struct TestStructWithEnum: ExAutoCodable {
    @ExCodable("enum") private(set)
    var `enum` = TestEnum.zero
}

5. Custom Encoding/Decoding

struct TestManualEncodeDecode: ExAutoCodable {
    @ExCodable(encode: { encoder, value in
        encoder["int"] = value <= 0 ? 0 : value
    }, decode: { decoder in
        if let int: Int = decoder["int"], int > 0 {
            return int
        }
        return 0
    }) private(set)
    var int: Int = 0
}

6. Type Conversions

ExCodable builtin type conversions:

  • boolean:
    • Bool
    • Int, Int8, Int16, Int32, Int64
    • UInt, UInt8, UInt16, UInt32, UInt64
    • String
  • integer:
    • Bool
    • Int, Int8, Int16, Int32, Int64
    • UInt, UInt8, UInt16, UInt32, UInt64
    • Double, Float
    • String
  • float:
    • Int, Int8, Int16, Int32, Int64
    • UInt, UInt8, UInt16, UInt32, UInt64
    • Double, Float
    • String
  • string:
    • Bool
    • Int, Int8, Int16, Int32, Int64
    • UInt, UInt8, UInt16, UInt32, UInt64
    • Double, Float
    • String

Custom type conversions for specific properties:

struct TestCustomEncodeDecode: ExAutoCodable {
    @ExCodable(decode: { decoder in
        if let string: String = decoder["string"] {
            return string.count
        }
        return 0
    }) private(set)
    var int: Int = 0
}

Custom type conversions for specific model:

struct TestCustomTypeConverter: ExAutoCodable {
    @ExCodable private(set)
    var doubleFromBool: Double? = nil
    @ExCodable private(set)
    var floatFromBool: Double? = nil
}

extension TestCustomTypeConverter: ExCodableDecodingTypeConverter {
    
    public static func decode<T: Decodable, K: CodingKey>(_ container: KeyedDecodingContainer<K>, codingKey: K, as type: T.Type) -> T? {
        
        // for nested optionals, e.g. `var int: Int??? = nil`
        let wrappedType = T?.wrappedType
        
        // decode Double from Bool
        if type is Double.Type || wrappedType is Double.Type {
            if let bool = try? container.decodeIfPresent(Bool.self, forKey: codingKey) {
                return (bool ? 1.0 : 0.0) as? T
            }
        }
        // decode Float from Bool
        else if type is Float.Type || wrappedType is Float.Type {
            if let bool = try? container.decodeIfPresent(Bool.self, forKey: codingKey) {
                return (bool ? 1.0 : 0.0) as? T
            }
        }
        
        return nil
    }
}

Custom type conversions for global:

struct TestCustomGlobalTypeConverter: ExAutoCodable, Equatable {
    @ExCodable("boolFromDouble") private(set)
    var boolFromDouble: Bool? = nil
}

extension ExCodableGlobalDecodingTypeConverter: ExCodableDecodingTypeConverter {
    
    public static func decode<T: Decodable, _K: CodingKey>(_ container: KeyedDecodingContainer<_K>, codingKey: _K, as type: T.Type) -> T? {
        
        // for nested optionals, e.g. `var int: Int??? = nil`
        let wrappedType = T?.wrappedType
        
        // decode Bool from Double
        if type is Bool.Type || wrappedType is Bool.Type {
            if let double = try? container.decodeIfPresent(Double.self, forKey: codingKey) {
                return (double != 0) as? T
            }
        }
        
        return nil
    }
}

7. Manual Encoding/Decoding using Subscripts

A type without ExCodable:

struct TestManualEncodingDecoding {
    let int: Int
    let string: String
}

Manual encoding/decoding using subscripts:

extension TestManualEncodingDecoding: Codable {
    
    enum Keys: CodingKey {
        case int, string
    }
    
    init(from decoder: Decoder) throws {
        int = decoder[Keys.int] ?? 0
        string = decoder[Keys.string] ?? ""
    }
    func encode(to encoder: Encoder) throws {
        encoder[Keys.int] = int
        encoder[Keys.string] = string
    }
}

8. return nil or throw error - UNSTABLE

While encoding/decoding, ExCodable ignores the keyNotFound, valueNotFound, invalidValue and typeMismatch errors and return nil by default, only throws JSON errors.

ExCodable also supports throw errors:

  • Use nonnull: true to throw EncodingError.invalidValue, DecodingError.keyNotFound, DecodingError.valueNotFound.
  • Use throws: true to throw DecodingError.typeMismatch.
struct TestNonnullAndThrows: ExAutoCodable {
    @ExCodable(nonnull: true, throws: true) private(set)
    var int: Int! = 0
}

9. class and Subclasses

class TestClass: ExAutoCodable {
    @ExCodable private(set)
    var int: Int = 0
    @ExCodable private(set)
    var string: String? = nil
    required init() {}
    init(int: Int, string: String?) {
        (self.int, self.string) = (int, string)
    }
}
class TestSubclass: TestClass {
    @ExCodable private(set)
    var bool: Bool = false
    required init() { super.init() }
    required init(int: Int, string: String, bool: Bool) {
        self.bool = bool
        super.init(int: int, string: string)
    }
}

10. Type Inference

struct TestStruct: ExAutoCodable, Equatable {
    @ExCodable private(set)
    var int: Int = 0
    @ExCodable private(set)
    var string: String? = nil
}

let json = Data(#"{"int":200,"string":"OK"}"#.utf8)
let model = try? TestStruct.decoded(from: json)

let dict = try? model.encoded() as [String: Any]
let copy = try? dict.decoded() as TestStruct

See the tests for more examples.

Requirements

  • iOS 12.0+ | tvOS 12.0+ | macOS 11.0+ | watchOS 4.0+
  • Xcode 15.4+
  • Swift 5.10+

Installation

Swift Package Manager:

.package(url: "https://github.com/ExCodable/ExCodable", from: "1.0.0")

CocoaPods:

pod 'ExCodable', '~> 1.0.0'

Migration

0.x to 1.x

When you update to ExCodable 1.0.

Step 1: Update your code to use the old API - DEPRECATED but quick.

  • Replace protocol ExCodable with ExCodableDEPRECATED.
  • Add static to func decodeForTypeConversion(_:codingKey:as:) of protocol KeyedDecodingContainerCustomTypeConversion.
struct TestExCodable {
    private(set) var int: Int = 0
    private(set) var string: String?
}

extension TestExCodable: ExCodableDEPRECATED {
    static let keyMapping: [KeyMap<Self>] = [
        KeyMap(\.int, to: "int"),
        KeyMap(\.string, to: "nested.nested.string", "string", "str", "s")
    ]
    init(from decoder: Decoder) throws {
        try decode(from: decoder, with: Self.keyMapping)
    }
}

Step 2: Upgrade your models to the new API one by one - SUGGESTED:

  • Replace protocol ExCodable with ExAutoCodable.
  • Remove initializer init(from decoder: Decoder) throws.
  • Remove static properties keyMapping.
  • Use @ExCodable("<key>", "<alt-key>", ...).
  • See Usage for more details.
struct TestExCodable: ExAutoCodable {
    @ExCodable private(set)
    var int: Int = 0
    @ExCodable("nested.nested.string", "string", "str", "s") private(set)
    var string: String?
}

Stars

Star Chart

Hope ExCodable will help you! Make a star ⭐️ 🤩

Thanks to

  • John Sundell (@JohnSundell) and the ideas from his Codextended
  • ibireme (@ibireme) and the features from his YYModel

About Me

  • Míng (@iwill) | [email protected]

License

ExCodable is released under the MIT license. See LICENSE for details.

About

JSON-Model Conversion with Swift Codable

ExCodable.iwill.im

Topics

alternative-keysannotationcodablecoding-keydecoderencoderjsonkey-mappingmodelnested-keysproperty-wrappersubscriptswifttype-conversions

Resources

Readme
MIT license
Activity
Custom properties

Stars

151 stars

Watchers

6 watching

Forks

22 forks
Report repository

Releases

Packages

Used by

Contributors

Languages

You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.