Swiftpack.co -  winddpan/CodableWrapper as Swift Package
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
winddpan/CodableWrapper
Codable + PropertyWrapper = ☕
.package(url: "https://github.com/winddpan/CodableWrapper.git", from: "0.1.1")

CodableWrapper

Codable + PropertyWrapper = ☕

  1. About The Project
  2. Installation
  3. Example
  4. How it works
  5. Advanced usage
  6. BuiltIn Transfroms
  7. License

About The Project

  • This project is use PropertyWrapper to improve your Codable use experience.
  • Simply based on JSONEncoder JSONDecoder.
  • Powerful and simplifily API than BetterCodable or CodableWrappers.
  • Pass configuration available:

    @CodableWrapper(codingKeys: ..., defaultValue: ...)
    @CodableWrapper(codingKeys: ..., transformer: ...)

  • Implement your own TransformType to do more stuff.
  • Auto fix basic type convertation, between String Number Bool ...

Installation

Cocoapods

pod 'CodableWrapper'

Swift Package Manager

https://github.com/winddpan/CodableWrapper

Example

struct NonCodable {
    var value: String?
}

struct ExampleModel: Codable {
    @CodableWrapper(codingKeys: ["stringVal", "string_Val"], defaultValue: "abc")
    var stringVal: String

    @CodableWrapper(codingKeys: ["int_Val", "intVal"], defaultValue: 123456)
    var intVal: Int

    @CodableWrapper(defaultValue: [1.998, 2.998, 3.998])
    var array: [Double]

    @CodableWrapper(defaultValue: false)
    var bool: Bool

    @CodableWrapper(transformer: TransformOf<NonCodable, String?>(fromNull: { NonCodable() }, 
                                                                  fromJSON: { NonCodable(value: $0) },
                                                                  toJSON: { $0.value }))
    var nonCodable: NonCodable

    @CodableWrapper(defaultValue: "default unImpl value")
    var unImpl: String
}


let json = """
{"int_Val": "233", "string_Val": "opq", "bool": "1", "nonCodable": "ok"}
"""

let model = try JSONDecoder().decode(ExampleModel.self, from: json.data(using: .utf8)!)
XCTAssertEqual(model.intVal, 233)
XCTAssertEqual(model.stringVal, "opq")
XCTAssertEqual(model.unImpl, "default unImpl value")
XCTAssertEqual(model.array, [1.998, 2.998, 3.998])
XCTAssertEqual(model.bool, true)
XCTAssertEqual(model.nonCodable.value, "ok")

How it works

struct DataModel: Codable {
    @CodableWrapper(defaultValue: "OK")
    var stringVal: String
}

/* Swift Build -> */
struct DataModel: Codable {
    var _stringVal = CodableWrapper<String>(defaultValue: "OK")

    var stringVal: String {
        get {
            return _stringVal.wrappedValue
        }
        set {
            _stringVal.wrappedValue = newValue
        }
    }

    enum CodingKeys: CodingKey {
        case stringVal
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        /* decode `newStringVal` */
        /* remember `newStringVal`: Thread.current.lastCodableWrapper = wrapper */
        /*
         extension KeyedDecodingContainer {
            func decode<Value>(_ type: CodableWrapper<Value>.Type, forKey key: Key) throws -> CodableWrapper<Value> {
                ...
                let wrapper = CodableWrapper<Value>(unsafed: ())
                Thread.current.lastCodableWrapper = wrapper
                ...
            }
         }
         */
        let newStringVal = try container.decode(CodableWrapper<String>.self, forKey: CodingKeys.stringVal)

        /* old `_stringVal` deinit */
        /* old `_stringVal` invokeAfterInjection called: transform old `_stringVal` Configs to `newStringVal` */
        /* 
         deinit {
             if !unsafeCreated, let construct = construct, let lastWrapper = Thread.current.lastCodableWrapper as? CodableWrapper<Value> {
                 lastWrapper.invokeAfterInjection(with: construct)
                 Thread.current.lastCodableWrapper = nil
             }
         }
        */
        self._stringVal = newStringVal
    }
}

Advanced usage

DefaultValue

DefaultValue should implement Codable protocol

struct ExampleModel: Codable {
    @CodableWrapper(defaultValue: false)
    var bool: Bool
}

let json = """
{"bool":"wrong value"}
"""

let model = try JSONDecoder().decode(ExampleModel.self, from: json.data(using: .utf8)!)
XCTAssertEqual(model.bool, false)

CodingKeys

While Decoding: try each CodingKey until succeed; while Encoding: use first CodingKey as Dictionary key

struct ExampleModel: Codable {
    @CodableWrapper(codingKeys: ["int_Val", "intVal"], defaultValue: 123456)
    var intVal: Int

    // Optional可以省略defaultValue,默认为nil
    @CodableWrapper(codingKeys: ["intOptional", "int_optional"])
    var intOptional: Int?
}

let json = """
{"int_Val": "233", "int_optional": 234}
"""

let model = try JSONDecoder().decode(ExampleModel.self, from: json.data(using: .utf8)!)
XCTAssertEqual(model.intVal, 233)
XCTAssertEqual(model.intOptional, 234)

let data = try JSONEncoder().encode(model)
let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
XCTAssertEqual(jsonObject["int_Val"] as? Int, 233)
XCTAssertEqual(jsonObject["intOptional"] as? Int, 234)

Transform

enum EnumInt: Int {
    case none, first, second, third
}
struct ExampleModel: Codable {
    @CodableWrapper(codingKeys: ["enum", "enumValue"],
                    transformer: TransformOf<EnumInt, Int>(fromNull: { EnumInt.none }, fromJSON: { EnumInt(rawValue: $0 + 1) }, toJSON: { $0.rawValue }))
    var enumValue: EnumInt
}

let json = """
{"enumValue": 2}
"""

let model = try JSONDecoder().decode(ExampleModel.self, from: json.data(using: .utf8)!)
XCTAssertEqual(model.enumValue, EnumInt.third)

let jsonData = try JSONEncoder().encode(model)
let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]
XCTAssertEqual(jsonObject["enum"] as? Int, 3)

let json2 = """
{"enum": 233}
"""
let model2 = try JSONDecoder().decode(ExampleModel.self, from: json2.data(using: .utf8)!)
XCTAssertEqual(model2.enumValue, EnumInt.none)

BasicTypeBridge

struct ExampleModel: Codable {
    // test init()
    @CodableWrapper()
    var int: Int?
    
    @CodableWrapper
    var string: String?

    @CodableWrapper
    var bool: Bool?
}

let json = """
{"int": "1", "string": 2, "bool": "true"}
"""

let model = try JSONDecoder().decode(ExampleModel.self, from: json.data(using: .utf8)!)
XCTAssertEqual(model.int, 1)
XCTAssertEqual(model.string, "2")
XCTAssertEqual(model.bool, true)

BuiltIn Transfroms

DateTransform

SecondsDateTransform / MillisecondDateTransform

struct ExampleModel: Codable {
    @CodableWrapper(transformer: SecondsDateTransform())
    var sencondsDate: Date

    @CodableWrapper(transformer: MillisecondDateTransform())
    var millSecondsDate: Date
}

let date = Date()
let json = """
{"sencondsDate": \(date.timeIntervalSince1970), "millSecondsDate": \(date.timeIntervalSince1970 * 1000)}
"""

let model = try JSONDecoder().decode(ExampleModel.self, from: json.data(using: .utf8)!)
XCTAssertEqual(model.sencondsDate.timeIntervalSince1970, date.timeIntervalSince1970)
XCTAssertEqual(model.millSecondsDate.timeIntervalSince1970, date.timeIntervalSince1970)

OmitCoding

struct ExampleModel: Codable {
    @CodableWrapper(transformer: OmitEncoding())
    var omitEncoding: String?

    @CodableWrapper(transformer: OmitDecoding())
    var omitDecoding: String?
}

let json = """
{"omitEncoding": 123, "omitDecoding": "abc"}
"""

let model = try JSONDecoder().decode(ExampleModel.self, from: json.data(using: .utf8)!)
XCTAssertEqual(model.omitEncoding, "123")
XCTAssertEqual(model.omitDecoding, nil)

let data = try JSONEncoder().encode(model)
let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
XCTAssertEqual(jsonObject["omitEncoding"] as? String, nil)
XCTAssertEqual(jsonObject["omitDecoding"] as? String, nil)

License

Distributed under the MIT License. See LICENSE for more information.

GitHub

link
Stars: 63
Last commit: 4 weeks ago

Ad: Job Offers

iOS Software Engineer @ Perry Street Software
Perry Street Software is Jack’d and SCRUFF. We are two of the world’s largest gay, bi, trans and queer social dating apps on iOS and Android. Our brands reach more than 20 million members worldwide so members can connect, meet and express themselves on a platform that prioritizes privacy and security. We invest heavily into SwiftUI and using Swift Packages to modularize the codebase.

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API