Skip to content

Codable customized using property wrappers library for Swift. πŸŒ‡

License

Notifications You must be signed in to change notification settings

yangKJ/HollowCodable

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

40 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

HollowCodable

CocoaPods Compatible CocoaPods Compatible Platform

HollowCodable is a codable customization using property wrappers library for Swift.

  • Make Complex Codable Serializate a breeze with declarative annotations!

Issues with native JSON Codable

  1. It does not support the custom coding key of a certain attribute. Once you have this requirement, either implement all coding keys manually to modify the desired coding key, or you have to set Decoder when decode, which is extremely inconvenient.
  2. Some non-Codable attributes cannot be ignored. The coding key needs to be implemented manually.
  3. Decode does not support multiple coding keys mapping to the same property.
  4. The default values of the model cannot be used, and it is obviously unreasonable to throw missing data errors instead of using the default values in the definition when decode data is missing.
  5. There is no support for simple type conversions, such as converting 0/1 to false/true, "123" to Int 123, or vice versa, who can ensure that the server will not accidentally modify the field type and cause the app to fail?
  6. Do not support Any property, many times our home page model will have a lot of submodels, but each submodel data structure is not the same, then need to decoding into Any, and then according to the module to confirm the submodel.

ok, All of these issues can be resolved after you use HollowCodable, so you're welcome!

Usage

  • Immutable: Made immutable via property wrapper, It can be used with other Coding.
struct YourModel: HollowCodable {
    @Immutable
    var id: Int
    
    @Immutable @BoolCoding
    var bar: Bool?
}
  • IgnoredKey: Add this to an Optional Property to not included it when Encoding or Decoding.
struct YourModel: HollowCodable {
    @IgnoredKey
    var ignorKey: String? = "Condy" // Be equivalent to use `lazy`.
    
    lazy var ignorKey2: String? = "Coi"
}
  • DefaultBacked: Set different default values for common types.
    • This library has built-in many default values, such as Int, Bool, String, Array...
    • if we want to set a different default value for the field.
struct YourModel: HollowCodable {
    @DefaultBacked<Int>
    var val: Int // If the field is not an optional, default is 0.
    
    @DefaultBacked<Bool>
    var boo: Bool // If the field is not an optional, default is false.
    
    @DefaultBacked<String>
    var string: String // If the field is not an optional, default is "".

    @DefaultBacked<AnyDictionary>
    var list: [String: Any] // If the field is not an optional, default is [:].
}

Data

  • Base64Coding: For a Data property that should be serialized to a Base64 encoded String.
struct YourModel: HollowCodable {
    @Base64Coding
    var base64Data: Data?
}
struct YourModel: HollowCodable {
    
    var base64Data: Data?
    
    static var codingKeys: [CodingKeyMapping] {
        return [
            CodingKeys.base64Data <-- "base64_data",
            CodingKeys.base64Data <-- Base64DataTransform(),
        ]
    }
}
And you can customize your own data (de)serialization type with DataValue.

Like:

public enum YourData: DataConverter {
    
    public typealias Value = String
    public typealias FromValue = Data
    public typealias ToValue = String
    
    public static let hasValue: Value = ""
    
    public static func transformToValue(with value: FromValue) -> ToValue? {
        // data to string..
    }
    
    public static func transformFromValue(with value: ToValue) -> FromValue? {
        // string to data..
    }
}

Used:
struct YourModel: HollowCodable {
    @AnyBacked<DataValue<YourData>>
    var data: Data?
}

Date

  • ISO8601DateCoding: Decodes String or TimeInterval values as an ISO8601 date.
  • RFC2822DateCoding: Decodes String or TimeInterval values as an RFC 2822 date.
  • RFC3339DateCoding: Decodes String or TimeInterval values as an RFC 3339 date.
struct YourModel: HollowCodable {
    @ISO8601DateCoding
    var iso8601: Date? // Now decodes to ISO8601 date.
    
    @RFC2822DateCoding
    var data1: Date? // Now decodes RFC 2822 date.
    
    @RFC3339DateCoding
    var data2: Date? // Now decodes RFC 3339 date.
}
  • SecondsSince1970DateCoding: For a Date property that should be serialized to SecondsSince1970.
  • MillisecondsSince1970DateCoding: For a Date property that should be serialized to MillisecondsSince1970.
struct YourModel: HollowCodable {
    @SecondsSince1970DateCoding
    var timestamp: Date // Now encodes to SecondsSince1970
    
    @MillisecondsSince1970DateCoding
    var timestamp2: Date // Now encodes to MillisecondsSince1970
}

Supports (de)serialization using different formats, like:

struct YourModel: HollowCodable {
    @DateCoding<Hollow.DateFormat.yyyy_mm_dd, Hollow.Timestamp.secondsSince1970>
    var time: Date? // Decoding use `yyyy-MM-dd` and Encoding use `seconds` timestamp.
}
struct YourModel: HollowCodable {
    
    var iso8601: Date? // Now decodes to ISO8601 date.
    
    var data1: Date? // Now decodes RFC 2822 date.
    
    var data2: Date? // Now decodes RFC 3339 date.
    
    var timestampDate: Date? // Now decodes seconds Since1970 date.
    
    static var codingKeys: [CodingKeyMapping] {
        return [
            TransformKeys(location: CodingKeys.iso8601, tranformer: ISO8601DateTransform()),
            CodingKeys.data1 <-- RFC2822DateTransform(),
            CodingKeys.data2 <-- RFC3339DateTransform(),
            CodingKeys.timestampDate <-- TimestampDateTransform(type: .seconds),
        ]
    }
}

UIColor/NSColor

  • HexColorCoding: For a Color property that should be serialized to a hex encoded String.
    • Support the hex string color with format #RGB、#RGBA、#RRGGBB、#RRGGBBAA.
  • RGBColorCoding: Decoding a red/green/blue to color.
  • RGBAColorCoding: Decoding a red/green/blue/alpha to color.
struct YourModel: HollowCodable {
    @HexColorCoding
    var color: HollowColor?
    
    @RGBColorCoding
    var background_color: HollowColor?
}

Bool

  • BoolCoding: Sometimes an API uses an Bool、Int or String for a booleans.
    • Uses <= 0 as false, and > 0 as true.
    • Uses lowercase "true"/"yes"/"y"/"t"/"1"/">0" as true,
    • Uses lowercase "false"/"no"/"f"/"n"/"0" as false.
  • FalseBoolCoding: If the field is not an optional type, default false value.
  • TrueBoolCoding: If the field is not an optional type, default true value.
struct YourModel: HollowCodable {
    @Immutable @BoolCoding
    var bar: Bool?
    
    @FalseBoolCoding
    var hasD: Bool
    
    @TrueBoolCoding
    var hasT: Bool
}

Enum

  • EnumCoding: To be convertable, An enum must conform to RawRepresentable protocol. Nothing special need to do now.
struct YourModel: HollowCodable {
    @EnumCoding<AnimalType>
    var animal: AnimalType? // 
}

enum AnimalType: String {
    case Cat = "cat"
    case Dog = "dog"
    case Bird = "bird"
}

NSDecimalNumber

  • NSDecimalNumberCoding: Decoding the String、Double、Float、CGFloat、Int or Int64 to a NSDecimalNumber property.
struct YourModel: HollowCodable {
    @DecimalNumberCoding
    var amount: NSDecimalNumber?
}

Any

  • DictionaryCoding: Support any value property wrapper with dictionary.
  • ArrayCoding: Support any value dictionary property wrapper with array.
  • AnyXCoding: Support for any general type,
"mixDict": {
    "sub": {
        "amount": "52.9",
    }, 
    "array": [{
        "val": 718,
    }, {
        "val": 911,
    }],
    "opt": null
}

struct YourModel: HollowCodable {
    @AnyBacked<AnyDictionary>
    var mixDict: [String: Any]? // Nested support.
    
    @AnyBacked<AnyDictionaryArray>
    var mixLiat: [[String: Any]]?
    
    @AnyBacked<AnyX>
    var x: Any? // Also nested support.
}

JSON

Supports decoding network api response data.
{
    "code": 200,
    "message": "test case.",
    "data": [{
        "id": 2,
        "title": "Harbeth Framework",
        "github": "https://github.com/yangKJ/Harbeth",
        "amount": "23.6",
        "hex_color": "#FA6D5B",
        "type": "text1",
        "timestamp" : 590277534,
        "bar": 1,
        "hasDefBool": 2,
        "time": "2024-05-29 23:49:55",
        "iso8601": null,
        "anyString": 5,
        "background_color": {
            "red": 255,
            "green": 128,
            "blue": 128
        },
        "dict": {
            "amount": "326.0"
        },
        "mixDict": {
            "sub": {
                "amount": "52.9",
            }, 
            "array": [{
                "val": 718,
            }, {
                "val": 911,
            }]
        },
        "list": [{
           "fruit": "Apple",
           "dream": null
        }, {
            "fruit": "Banana",
            "dream": "Night"
         }]
    }, {
        "id": 7,
        "title": "Network Framework",
        "github": "https://github.com/yangKJ/RxNetworks",
        "amount": 120.3,
        "hex_color2": "#1AC756",
        "type": null,
        "timestamp" : 590288534,
        "bar": null,
        "hasDefBool": null,
        "time": "2024-05-29 20:23:46",
        "iso8601": "2023-05-23T09:43:38Z",
        "anyString": null,
        "background_color": null,
        "dict": null,
        "mixDict": null,
        "list": null
    }]
}
  • You can used like:
let datas = ApiResponse<[YourModel]>.deserialize(from: json)?.data
Convert between Model and JSON.
json = """
{
     "uid":888888,
     "name": "Condy",
     "age": 18,
     "time": 590277534
}
"""

struct YourModel: HollowCodable {
    @Immutable
    var uid: Int?
    
    @DefaultBacked<Int>
    var age: Int // If the field is not an optional, is 0.
    
    @AnyBacked<String>
    var named: String? // Support multiple keys.
    
    var time: Date? // Like to HandyJSON mode `TransformType`.
    
    static var codingKeys: [CodingKeyMapping] {
        return [
            //CodingKeys.named <-- ["name", "named"],
            ReplaceKeys(location: CodingKeys.named, keys: "name", "named"),
            //CodingKeys.time <-- TimestampDateTransform(),
            TransformKeys(location: CodingKeys.time, tranformer: TimestampDateTransform()),
        ]
    }
}

// JSON to Model.
let model = YourModel.deserialize(from: json)

// Model to JSON
let json = model.toJSONString(prettyPrint: true)

Available Property Wrappers

And support customization, you only need to implement the Transformer protocol.

It also supports the attribute wrapper that can set the default value, and you need to implement the HasDefaultValuable protocol.

Available Transforms

And support customization, you only need to implement the TransformType protocol.

⚠️ Note: At present, the converter only supports Data and Date, in order to be as compatible with HandyJSON as possible.

Booming

Booming is a base network library for Swift. Developed for Swift 5, it aims to make use of the latest language features. The framework's ultimate goal is to enable easy networking that makes it easy to write well-maintainable code.

This module is serialize and deserialize the data, Replace HandyJSON.

🎷 Example of use in conjunction with the network part:

func request(_ count: Int) -> Observable<[CodableModel]> {
    CodableAPI.cache(count)
        .request(callbackQueue: DispatchQueue(label: "request.codable"))
        .deserialized(ApiResponse<[CodableModel]>.self)
        .compactMap({ $0.data })
        .observe(on: MainScheduler.instance)
        .catchAndReturn([])
}
RxSwift deserialized extension.
public extension Observable where Element: Any {
    
    @discardableResult func deserialized<T>(_ type: T.Type) -> Observable<T> where T: HollowCodable {
        return self.map { element -> T in
            return try T.deserialize(element: element)
        }
    }
    
    @discardableResult func deserialized<T>(_ type: [T].Type) -> Observable<[T]> where T: HollowCodable {
        return self.map { element -> [T] in
            return try [T].deserialize(element: element)
        }
    }
    
    @discardableResult func deserialized<T>(_ type: T.Type) -> Observable<ApiResponse<T.DataType>> where T: HasResponsable, T.DataType: HollowCodable {
        return self.map { element -> ApiResponse<T.DataType> in
            return try T.deserialize(element: element)
        }
    }
    
    @discardableResult func deserialized<T>(_ type: T.Type) -> Observable<ApiResponse<[T.DataType.Element]>> where T: HasResponsable, T.DataType: Collection, T.DataType.Element: HollowCodable {
        return self.map { element -> ApiResponse<[T.DataType.Element]> in
            return try T.deserialize(element: element)
        }
    }
}

CocoaPods

CocoaPods is a dependency manager. For usage and installation instructions, visit their website. To integrate using CocoaPods, specify it in your Podfile:

If you wang using Codable:

pod 'HollowCodable'

Remarks

The general process is almost like this, the Demo is also written in great detail, you can check it out for yourself.🎷

CodableExample

Tip: If you find it helpful, please help me with a star. If you have any questions or needs, you can also issue.

Thanks.πŸŽ‡

About the author

Buy me a coffee or support me on GitHub.

yellow-button

License

Booming is available under the MIT license. See the LICENSE file for more info.


About

Codable customized using property wrappers library for Swift. πŸŒ‡

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published