Enhancement of current codable protocol using property wrappers for easier access and better [en/de]coding of JSON. The goal of these property wrappers is to avoid implementing custom initializer for decoder, encoder, sometimes coding keys and suffer through boilerplate code.
- SGSerializable
- Typed alias with
SGDecodable
&SGEncodable
- Custom [en/de]coding using property wrappers.
- This protocol is required for use property wrappers mentioned below.
- No need to write your own custom
init(from decoder: Decoder) throws
andfunc encode(to encoder: Encoder) throws
- Non need to write custom CodingKeys.
- Custom Decoding key
- Use property name if key isn't mentioned.
- Works with inheritance and composition.
- Custom Transformer implementation
- Default transformer classes i.e.
StringToURL
etc. List is available below. - Default Value if decoding failed or not provided. You can provide own default value as well.
@SGSerializable(default: "", key: "your_key", type: .auto)
var key: String?
- Custom tranformable classes if required or you can use default ones.
- Transforming objects to your own type like
String
to NativeURL
.
struct Foo: SGCodable {
@SGSerializable
var key: String?
@SGSerializable(default: [])
var items: [String]
@SGSerializable(key: "crypto")
var cryptography: [String: String]
@SGSerializable
var values: [Double]?
}
struct Person: SGCodable {
@SGTransformSerializable<StringToURL>
var profilePictureURL: URL?
}
- iOS 11.0+ / macOS 10.15+ / tvOS 11.0+ / watchOS 6.0+
- Xcode 13+
- Swift 5.5+
Swift Package Manager
URL:
https://github.com/rehannali/SGSerializable.git
Add this to the depedencies in your Package.swift
manifest file.
dependencies: [
.package(url: "https://github.com/rehannali/SGSerializable.git")
]
- Mark all properties with
SGSerializable
. - Mark your model class/struct with
SGCodable
. - You can use either
SGDecodable
orSGEncodable
for decoding and encoding respectively.
class Person: SGDecodable {
@SGSerializable(type: .string)
var name: String?
@SGSerializable(key: "age")
var age: Int
@SGSerializable(default: "", key: "city")
var city: String
@SGSerializable(default: Animal(), key: "animal")
var animmal: Animal?
required init() {}
}
class Role: Person {
@SGSerializable
var role: String
}
struct Animal: SGDecodable {
@SGSerializable
var name: String?
}
{
"name": "John",
"age": "12",
"city": "London"
}
Above mentioned JSON model successfully decode into Person
class. It will successfully decode age: String
into age: Int
.
If type is provided, it'll use that type to downcast the value. Default: .auto
will be used.
Other available
CodingTypes
are mentioned below.
CodingType
case int
case double
case float
case bool
case string
case auto
case none
struct Person: SGCodable {
init(name: String?) {
self.$name = name
}
@SGSerializable
var name: String
}
You can create own transormation by conforming to SGTranformable
.
struct StringToBool: SGTranformable {
typealias FromType = String
typealias ToType = Bool
static func transform(from value: String?) -> Bool? {
guard let value = value else { return nil }
switch value {
case "true": return true
default: return false
}
}
static func transform(from value: Bool?) -> String? {
guard let value = value else { return nil }
let stringValue = value ? "true" : "false"
return stringValue
}
}
struct Person: SGCodable {
@SGTransformSerializable<StringToBool>
var isLogin: Bool?
}
You can access optional values by access attributes directly.
let person = Person()
let isLogin = person.isLogin
now isLogin
is optional for transform classes.
You can access non optional values like person.$isLogin
. It can automatically gives you default value of else fatalError
if not found because of programming error. Make sure you provide default value for custom objects if you want to access non-optional value.
struct Person: SGCodable {
init(isLogin: Bool?) {
self.isLogin = isLogin
}
@SGTransformSerializable<StringToBool>
var isLogin: Bool?
}
Defaut Transformer Classes
StringToBase64Data
StringToDateISO8601
StringToDateRFC3339
TimeIntervalToDate
MilliSecondsToDate
StringToURL
There are some cases when you don't want to encode to decode attributes. You can do it by simply add variable and don't use the property wrappers for that attribute.
struct Animal: SGCodable {
@SGSerializable
var name: String?
// This will omit from [en/de]coding
var omitVariable: String?
}
There is time we need to add custom function or computed property to provide dictionary from object as we write it manually.
Now it is possible by conforming your class to NSObject
or NSObjectProtocol
and get dictionary with object.swiftDictionary
or you can convert any class and struct to dictionary by conforming to SGDictionaryConverter
.
class Person: NSObject {
var name = "Frank"
var age = -1
var address: String?
}
let person = Person()
person.swiftDictionary
class Address: SGDictionaryConverter {
var street: String?
var city: String?
var state: String?
var country: String?
}
class Person: Address {
var name = "Frank"
var age = -1
}
let person = Person()
person.toDictionary()
struct Person: SGDictionaryConverter {
var name = "Frank"
var age = -1
var address: Address?
}
struct Address: SGDictionaryConverter {
var street: String?
var city: String?
var state: String?
var country: String?
}
let person = Person()
person.toDictionary()
Inheritance and Composition works out of the box using
SGDictionaryConverter
.
If you want to omit optional or default values like empty string or nil, you can use strippingNullOrDefaults()
to any Array
or Dictionary
.
let person = Person()
person.swiftDictionary.strippingNullOrDefaults()
it will omit default and nil values which can be useful while performing network request.
End Result:
["name": "Frank"]
Inheritance and Composition works out of the box with swiftDictionary
and strippingNullOrDefaults()
Define class/struct using required protocol SGCodable
.
struct Person {
@SGSerializable
var name: String?
}
let person = try Person.initialize(with: jsonData)
let person = try Person.initialize(with: jsonString)
let person = try Person.initialize(from: jsonURL)
or you can use like this:
let person = try [Person].initialize(with: jsonData)
let person = try [Person].initialize(with: jsonString)
let person = try [Person].initialize(from: jsonURL)
let jsonString = person.jsonString()
let jsonData = person.jsonData()
let dictionary = person.dictionary
This is only a tip of the iceberg of what we can achieve using Property Wrappers and how it can be improved [de/en]coding of JSON using property wrappers in Swift. If there is a any custom tranformation that could be added feel free to open an issue requesting it and/or submit a pull request with the new option.