Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save huynguyencong/6c7105754020b4f55d1a95c59a748da7 to your computer and use it in GitHub Desktop.
Save huynguyencong/6c7105754020b4f55d1a95c59a748da7 to your computer and use it in GitHub Desktop.
Use property wrapper to reduce meaningless code in a decodable, when you need to customize how to parse just 1 key, but have to implement all keys. It makes decoding a custom key using Codable is short and simple like TransformerOf of ObjectMapper open source.
// IMPORTANCE: I don't use this anymore, because it has problem with a json that missing key. In normal, we can ignore a missing key by set a property optional. But in this case, setting property optional still causes no key error.
/* Huy Nguyen
Use property wrapper to reduce meaningless code in a decodable, when you need to customize how to parse just 1 key, but have to implement all keys.
This snippet assumes you need to decode some models that currency values are Strings with "$" prefix ("$20.3" to 20.3), and you want to parse them to Double values.
For example, the JSON of Product model:
{
"price": "$20.3",
"offerPrice": "$12",
"amount": 3
}
Normally, you have to implelement `init(from decoder: Decoder)` and `encode(to encoder: Encoder)`, then encode/decode all properties, even the properties that can be encoded/decoded by default. In case you have 100 properties like that, you have to write a lot of meaningless code. See the original Product model at the bottom.
This property wrapper will help you to avoid it.
*/
struct Product: Codable {
@CurrencyTransformer var price: Double
@CurrencyTransformer var offerPrice: Double
var amount: Int
}
/// Parse JSON string with "$" symbol prefix to double value
@propertyWrapper
struct CurrencyTransformer: Codable {
var wrappedValue: Double
init(wrappedValue: Double) {
self.wrappedValue = wrappedValue
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
var stringValue = try container.decode(String.self)
stringValue = stringValue.replacingOccurrences(of: "$", with: "")
wrappedValue = Double(stringValue) ?? 0
}
func encode(to encoder: Encoder) throws {
let stringValue = "$\(wrappedValue)"
var container = encoder.singleValueContainer()
try container.encode(stringValue)
}
}
// Testing
let jsonString = """
{
"price": "$20.3",
"offerPrice": "$12",
"amount": 3
}
"""
if let data = jsonString.data(using: .utf8) {
do {
// Decoding test
let product = try JSONDecoder().decode(Product.self, from: data)
print("Decoded. Product price: \(product.price)")
// Encoding test
do {
let encodedData = try JSONEncoder().encode(product)
let encodedJSONString = String(data: encodedData, encoding: .utf8)
print("Encoded. String: \(encodedJSONString ?? "")")
}
catch {
print("Encoding error: \(error)")
}
}
catch {
print("Decoding error: \(error)")
}
}
/* This is Product model code when we don't use above property wrapper. It may be longer if your model has more than 3 properties like in this example.
struct Product: Codable {
var price: Double
var offerPrice: Double
var amount: Int
enum CodingKeys: String, CodingKey {
case price
case offerPrice
case amount
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let priceString = try container.decode(String.self, forKey: .price)
price = Self.currencyDouble(from: priceString)
let offerPriceString = try container.decode(String.self, forKey: .offerPrice)
offerPrice = Self.currencyDouble(from: offerPriceString)
amount = try container.decode(Int.self, forKey: .amount)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let priceString = Self.currencyString(from: price)
try container.encode(priceString, forKey: .price)
let offerPriceString = Self.currencyString(from: offerPrice)
try container.encode(offerPriceString, forKey: .offerPrice)
try container.encode(amount, forKey: .amount)
}
private static func currencyDouble(from string: String) -> Double {
let stringValue = string.replacingOccurrences(of: "$", with: "")
return Double(stringValue) ?? 0
}
private static func currencyString(from value: Double) -> String {
return "$\(value)"
}
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment