Skip to content

a594508257/CodableWrapper

 
 

Repository files navigation

Requirements

Xcode Minimun Deployments Version
Xcode15 >= iOS13 / macOS11 1.0
< Xcode15 < iOS13 / macOS11 0.3.3

About

The project objective is to enhance the usage experience of the Codable protocol using the macro provided by Swift 5.9 and to address the shortcomings of various official versions.

Feature

  • Default value
  • Basic type automatic convertible, between String Bool Number etc.
  • Custom multiple CodingKey
  • Nested Dictionary CodingKey
  • Automatic compatibility between camel case and snake case
  • Convenience Codable subclass
  • Transformer

Installation

Cocoapods

pod 'CodableWrapper', '0.3.3'

No longer supported since 1.0.0, use version 0.3.3 if your project uses <Xcode 15 or <iOS 13.

Swift Package Manager

https://github.com/winddpan/CodableWrapper

Example

@Codable
struct BasicModel {
    var defaultVal: String = "hello world"
    var defaultVal2: String = Bool.random() ? "hello world" : ""
    let strict: String
    let noStrict: String?
    let autoConvert: Int?

    @CodingKey("hello")
    var hi: String = "there"

    @CodingNestedKey("nested.hi")
    @CodingTransformer(StringPrefixTransform("HELLO -> "))
    var codingKeySupport: String

    @CodingNestedKey("nested.b")
    var nestedB: String

    var testGetter: String {
        nestedB
    }
}

final class CodableWrapperTests: XCTestCase {
    func testBasicUsage() throws {
        let jsonStr = """
        {
            "strict": "value of strict",
            "autoConvert": "998",
            "nested": {
                "hi": "nested there",
                "b": "b value"
            }
        }
        """

        let model = try JSONDecoder().decode(BasicModel.self, from: jsonStr.data(using: .utf8)!)
        XCTAssertEqual(model.defaultVal, "hello world")
        XCTAssertEqual(model.strict, "value of strict")
        XCTAssertEqual(model.noStrict, nil)
        XCTAssertEqual(model.autoConvert, 998)
        XCTAssertEqual(model.hi, "there")
        XCTAssertEqual(model.codingKeySupport, "HELLO -> nested there")
        XCTAssertEqual(model.nestedB, "b value")

        let encoded = try JSONEncoder().encode(model)
        let dict = try JSONSerialization.jsonObject(with: encoded) as! [String: Any]
        XCTAssertEqual(model.defaultVal, dict["defaultVal"] as! String)
        XCTAssertEqual(model.strict, dict["strict"] as! String)
        XCTAssertNil(dict["noStrict"])
        XCTAssertEqual(model.autoConvert, dict["autoConvert"] as? Int)
        XCTAssertEqual(model.hi, dict["hello"] as! String)
        XCTAssertEqual("nested there", (dict["nested"] as! [String: Any])["hi"] as! String)
        XCTAssertEqual(model.nestedB, (dict["nested"] as! [String: Any])["b"] as! String)
    }
}

Macro usage

@Codable

  • Auto conformance Codable and CodableObversable protocol if not explicitly declared

    // both below works well
    
    @Codable
    struct BasicModel {}
    
    @Codable
    struct BasicModel: Codable {}
    
    @Codable
    struct BasicModel: Codable, CodableObversable {}
    
    @Codable
    struct BasicModel {
        var age: Int = 0
        mutating func didDecodeFinish() {
            age = 1
        }
    }
  • Default value

    @Codable
    struct TestModel {
        let name: String
        var balance: Double = 0
    }
    
    // { "name": "jhon" }
  • Basic type automatic convertible, between String Bool Number etc.

    @Codable
    struct TestModel {
        let autoConvert: Int?
    }
    
    // { "autoConvert": "998" }
  • Automatic compatibility between camel case and snake case

    @Codable
    struct TestModel {
        var userName: String = ""
    }
    
    // { "user_name": "jhon" }
  • Member Wise Init

    @Codable
    public struct TestModel {
        public var userName: String = ""
    
        // Automatic generated
        public init(userName: String = "") {
            self.userName = userName
        }
    }

@CodingKey

  • Custom CodingKeys

    @Codable
    struct TestModel {
        @CodingKey("u1", "u2", "u9")
        var userName: String = ""
    }
    
    // { "u9": "jhon" }

@CodingNestedKey

  • Custom CodingKeys in nested dictionary

    @Codable
    struct TestModel {
        @CodingNestedKey("data.u1", "data.u2", "data.u9")
        var userName: String = ""
    }
    
    // { "data": {"u9": "jhon"} }

@CodingIgnoreKey

  • Custom Coding ignore in Model

    @Codable
    struct TestModel {
        @CodingIgnoreKey
        var userName: String = ""
    }
    
    // { "userName": "jhon" }
    // result: userName is nil

@CodableSubclass

  • Automatic generate Codable class's subclass init(from:) and encode(to:) super calls

    @Codable
    class BaseModel {
        let userName: String
    }
    
    @CodableSubclass
    class SubModel: BaseModel {
        let age: Int
    }
    
    // {"user_name": "jhon", "age": 22}

@CodingTransformer

  • Transformer between in Codable / NonCodable model

    struct DateWrapper {
        let timestamp: TimeInterval
    
        var date: Date {
            Date(timeIntervalSince1970: timestamp)
        }
    
        init(timestamp: TimeInterval) {
            self.timestamp = timestamp
        }
    
        static var transformer = TransformOf<DateWrapper, TimeInterval>(fromJSON: { DateWrapper(timestamp: $0 ?? 0) }, toJSON: { $0.timestamp })
    }
    
    @Codable
    struct DateModel {
        @CodingTransformer(DateWrapper.transformer)
        var time: DateWrapper? = DateWrapper(timestamp: 0)
        
        @CodingTransformer(DateWrapper.transformer)
        var time1: DateWrapper = DateWrapper(timestamp: 0)
        
        @CodingTransformer(DateWrapper.transformer)
        var time2: DateWrapper?
    }
    
    class TransformTest: XCTestCase {
        func testDateModel() throws {
            let json = """
            {"time": 12345}
            """
    
            let model = try JSONDecoder().decode(DateModel.self, from: json.data(using: .utf8)!)
            XCTAssertEqual(model.time?.timestamp, 12345)
            XCTAssertEqual(model.time?.date.description, "1970-01-01 03:25:45 +0000")
    
            let encode = try JSONEncoder().encode(model)
            let jsonObject = try JSONSerialization.jsonObject(with: encode, options: []) as! [String: Any]
            XCTAssertEqual(jsonObject["time"] as! TimeInterval, 12345)
        }
    }

Podspec Config

  • Swift Package will be build automatically when you first pod install, the first build need a few time.

  • This means that when there is a relase shortcut in the directory CodableWrapper/.build, the Package builds successfully.

  • There is a situation that pod install will build automatically, if the Package auto build is not completed, the build will fail when you build the project.

    script = <<-SCRIPT
      env -i PATH="$PATH" "$SHELL" -l -c "swift build -c release --package-path \\"$PODS_TARGET_SRCROOT\\" --build-path \\"${PODS_BUILD_DIR}/CodableWrapper\\""
      SCRIPT
    
  • The first important setting, it's used to set the SwiftMacros plugin path for the main project.

  • You need to replace this with your custom path

    post_install do |installer|
      installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
          config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -Xfrontend -load-plugin-executable -Xfrontend $(PODS_BUILD_DIR)/CodableWrapper/release/CodableWrapperMacros#CodableWrapperMacros'
        end
      end
    end
    
  • The second important setting, both of them are used to set the SwiftMacros plugin path for the Development Pods in the Pod project.

  • You need to replace them with your custom path

    s.pod_target_xcconfig = {
      "OTHER_SWIFT_FLAGS" => "-Xfrontend -load-plugin-executable -Xfrontend $(PODS_BUILD_DIR)/CodableWrapper/release/CodableWrapperMacros#CodableWrapperMacros"
    }
        
    s.user_target_xcconfig = {
      "OTHER_SWIFT_FLAGS" => "-Xfrontend -load-plugin-executable -Xfrontend $(PODS_BUILD_DIR)/CodableWrapper/release/CodableWrapperMacros#CodableWrapperMacros"
    }
    

About

@codingkey("encoder", "decoder") var cool: Bool = true

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Swift 95.9%
  • Ruby 4.0%
  • C 0.1%