Skip to content

Instantly share code, notes, and snippets.

@darrarski
Last active November 26, 2021 14:23
Show Gist options
  • Save darrarski/69502f12961629ef049d5f63eb81ac1d to your computer and use it in GitHub Desktop.
Save darrarski/69502f12961629ef049d5f63eb81ac1d to your computer and use it in GitHub Desktop.
Swift Codable, Decimal <~> String encoding
import Quick
import Nimble
class CodableDecimalSpec: QuickSpec {
override func spec() {
context("encode Model to JSON") {
var model: Model!
var json: [String: String]?
beforeEach {
model = Model(value: 123.45)
let data = try! JSONEncoder().encode(model)
let jsonObject = try! JSONSerialization.jsonObject(with: data, options: [])
json = jsonObject as? [String: String]
}
it("should encode correct JSON") {
expect(json) == ["value": "123.45"]
}
}
context("decode Model from valid JSON") {
var json: [String: String]!
var model: Model!
beforeEach {
json = ["value": "543.21"]
let data = try! JSONSerialization.data(withJSONObject: json, options: [])
model = try! JSONDecoder().decode(Model.self, from: data)
}
it("should decode correct Model") {
expect(model) == Model(value: 543.21)
}
}
context("decode Model from invalid JSON") {
var json: [String: String]!
var throwedError: Error?
beforeEach {
json = ["value": "abcd"]
do {
let data = try JSONSerialization.data(withJSONObject: json, options: [])
_ = try JSONDecoder().decode(Model.self, from: data)
} catch {
throwedError = error
}
}
it("should throw error") {
expect(throwedError).notTo(beNil())
let string = String(describing: throwedError as? DecodingError)
expect(string).to(contain("Invalid decimal value (abcd)"))
}
}
}
}
private struct Model: Codable, Equatable {
let value: Decimal
init(value: Decimal) {
self.value = value
}
enum CodingKeys: String, CodingKey {
case value
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
value = try container.decodeDecimal(forKey: .value)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeDecimal(value, forKey: .value)
}
}
import Foundation
extension KeyedDecodingContainer {
func decodeDecimal(forKey key: K) throws -> Decimal {
let string = try decode(String.self, forKey: key)
guard let decimal = Decimal(string: string) else {
let desc = "Invalid decimal value (\(string))"
let error = DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: desc)
throw error
}
return decimal
}
}
import Foundation
extension KeyedEncodingContainer {
mutating func encodeDecimal(_ decimal: Decimal, forKey key: K) throws {
let decimalNumber = NSDecimalNumber(decimal: decimal)
let string = decimalNumber.stringValue
try encode(string, forKey: key)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment