CodableWrapper 0.3.3

CodableWrapper 0.3.3

Maintained by winddpan.



  • By
  • winddpan and scyano

CodableWrapper

Codable + PropertyWrapper = @Codec("encoder", "decoder") var cool: Bool = true

  1. About
  2. Feature
  3. Installation
  4. Example
  5. How it works
  6. Usage

中文说明

About

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

Feature

  • Default value supported
  • Basic type convertible, between String Bool Number
  • Custom key support
  • Fix parsing failure due to missing fields from server
  • Fix parsing failure due to mismatch Enum raw value
  • Custom transform

Installation

Cocoapods

pod 'CodableWrapper'

Swift Package Manager

https://github.com/winddpan/CodableWrapper

Example

enum Animal: String, Codable {
    case dog
    case cat
    case fish
}

struct ExampleModel: Codable {
    @Codec("aString")
    var stringVal: String = "scyano"

    @Codec("aInt")
    var intVal: Int = 123456

    @Codec var defaultArray: [Double] = [1.998, 2.998, 3.998]

    @Codec var bool: Bool = false

    @Codec var unImpl: String?
    
    @Codec var animal: Animal = .dog
}

let json = #"{"aString": "pan", "aInt": "233", "bool": "1", "animal": "cat"}"#

let model = try JSONDecoder().decode(ExampleModel.self, from: json.data(using: .utf8)!)
XCTAssertEqual(model.stringVal, "pan")
XCTAssertEqual(model.intVal, 233)
XCTAssertEqual(model.defaultArray, [1.998, 2.998, 3.998])
XCTAssertEqual(model.bool, true)
XCTAssertEqual(model.unImpl, nil)
XCTAssertEqual(model.animal, .cat)

For more examples, please check the unit tests or Playground

How it works

struct DataModel: Codable {
    @Codec var stringVal: String = "OK"
}

/* pseudocode from Swift open source lib: Codable.Swift -> */
struct DataModel: Codable {
    private var _stringVal = Codec<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: Codec<Value>.Type, forKey key: Key) throws -> Codec<Value> {
                ...
                let wrapper = Codec<Value>(unsafed: ())
                Thread.current.lastCodableWrapper = wrapper
                ...
            }
         }
         */
        let newStringVal = try container.decode(Codec<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? Codec<Value> {
                 lastWrapper.invokeAfterInjection(with: construct)
                 Thread.current.lastCodableWrapper = nil
             }
         }
        */
        self._stringVal = newStringVal
    }
}

Usage

DefaultValue

DefaultValue should implement Codable protocol

struct ExampleModel: Codable {
    @Codec var bool: Bool = false
}

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

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

Auto snake camel convert

struct ExampleModel: Codable {
    @Codec var snake_string: String = ""
    @Codec var camelString: String = ""
}

let json = #"{"snakeString":"snake", "camel_string": "camel"}"#

let model = try JSONDecoder().decode(ExampleModel.self, from: json.data(using: .utf8)!)
XCTAssertEqual(model.snake_string, "snake")
XCTAssertEqual(model.camelString, "camel")

CodingKeys

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

struct ExampleModel: Codable {
    @Codec("int_Val", "intVal")
    var intVal: Int = 123456

    @Codec("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)

Basic type bridging

struct ExampleModel: Codable {
    @Codec var int: Int?
    @Codec var string: String?
    @Codec 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)

Transformer

struct User: Codable {
    @Codec(transformer: SecondDateTransform())
    var registerDate: Date?
}       
let date = Date()
let json = #"{"sencondsDate": \(date.timeIntervalSince1970)}"#

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

It also support custom transformer, your CustomTransformer only need to comfirm to TransfromType

License

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