Last active
May 28, 2021 10:15
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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