diff --git a/Sources/IkigaJSON/Codable/JSONDecoder.swift b/Sources/IkigaJSON/Codable/JSONDecoder.swift index 4ae7145..01ed83f 100644 --- a/Sources/IkigaJSON/Codable/JSONDecoder.swift +++ b/Sources/IkigaJSON/Codable/JSONDecoder.swift @@ -1,6 +1,11 @@ import Foundation import NIO +enum JSONDecoderError: Error { + case invalidURL(String) + case invalidDecimal(String) +} + @available(OSX 10.12, iOS 10, *) let isoFormatter = ISO8601DateFormatter() let isoDateFormatter: DateFormatter = { @@ -281,6 +286,17 @@ fileprivate struct _JSONDecoder: Decoder { @unknown default: throw JSONParserError.unknownJSONStrategy } + case is URL.Type: + let string = try singleValueContainer().decode(String.self) + + guard let url = URL(string: string) else { + throw JSONDecoderError.invalidURL(string) + } + + return url as! D + case is Decimal.Type: + let double = try singleValueContainer().decode(Double.self) + return Decimal(floatLiteral: double) as! D default: break } diff --git a/Sources/IkigaJSON/Codable/JSONEncoder.swift b/Sources/IkigaJSON/Codable/JSONEncoder.swift index 6e63855..d04f7a9 100644 --- a/Sources/IkigaJSON/Codable/JSONEncoder.swift +++ b/Sources/IkigaJSON/Codable/JSONEncoder.swift @@ -151,21 +151,21 @@ final class SharedEncoderData { /// Inserts the other autdeallocated storage into this storage func insert(contentsOf storage: SharedEncoderData, count: Int, at offset: inout Int) { beforeWrite(offset: offset, count: count) - self.pointer.advanced(by: offset).assign(from: storage.pointer, count: count) + self.pointer.advanced(by: offset).update(from: storage.pointer, count: count) offset = offset &+ count } /// Inserts the other autdeallocated storage into this storage func insert(contentsOf data: [UInt8], at offset: inout Int) { beforeWrite(offset: offset, count: data.count) - self.pointer.advanced(by: offset).assign(from: data, count: data.count) + self.pointer.advanced(by: offset).update(from: data, count: data.count) offset = offset &+ data.count } /// Inserts the other autdeallocated storage into this storage func insert(contentsOf storage: StaticString, at offset: inout Int) { beforeWrite(offset: offset, count: storage.utf8CodeUnitCount) - self.pointer.advanced(by: offset).assign(from: storage.utf8Start, count: storage.utf8CodeUnitCount) + self.pointer.advanced(by: offset).update(from: storage.utf8Start, count: storage.utf8CodeUnitCount) offset = offset &+ storage.utf8CodeUnitCount } @@ -175,7 +175,7 @@ final class SharedEncoderData { let utf8 = string.utf8 let count = utf8.withContiguousStorageIfAvailable { utf8String -> Int in self.beforeWrite(offset: writeOffset, count: utf8String.count) - self.pointer.advanced(by: writeOffset).assign( + self.pointer.advanced(by: writeOffset).update( from: utf8String.baseAddress!, count: utf8String.count ) @@ -187,7 +187,7 @@ final class SharedEncoderData { } else { let count = utf8.count let buffer = Array(utf8) - self.pointer.advanced(by: writeOffset).assign( + self.pointer.advanced(by: writeOffset).update( from: buffer, count: count ) @@ -366,10 +366,14 @@ fileprivate final class _JSONEncoder: Encoder { case .deferredToDate: return false case .secondsSince1970: - key.map { writeKey($0) } + if let key = key { + writeKey(key) + } writeValue(date.timeIntervalSince1970) case .millisecondsSince1970: - key.map { writeKey($0) } + if let key = key { + writeKey(key) + } writeValue(date.timeIntervalSince1970 * 1000) case .iso8601: let string: String @@ -384,11 +388,15 @@ fileprivate final class _JSONEncoder: Encoder { writeValue(string) case .formatted(let formatter): let string = formatter.string(from: date) - key.map { writeKey($0) } + if let key = key { + writeKey(key) + } writeValue(string) case .custom(let custom): let offsetBeforeKey = offset, hadWrittenValue = didWriteValue - key.map { writeKey($0) } + if let key = key { + writeKey(key) + } let encoder = _JSONEncoder(codingPath: codingPath, userInfo: userInfo, data: self.data) try custom(date, encoder) if encoder.didWriteValue { @@ -409,11 +417,15 @@ fileprivate final class _JSONEncoder: Encoder { return false case .base64: let string = data.base64EncodedString() - key.map { writeKey($0) } + if let key = key { + writeKey(key) + } writeValue(string) case .custom(let custom): let offsetBeforeKey = offset, hadWrittenValue = didWriteValue - key.map { writeKey($0) } + if let key = key { + writeKey(key) + } let encoder = _JSONEncoder(codingPath: codingPath, userInfo: userInfo, data: self.data) try custom(data, encoder) if encoder.didWriteValue { @@ -427,6 +439,19 @@ fileprivate final class _JSONEncoder: Encoder { throw JSONParserError.unknownJSONStrategy } + return true + case let url as URL: + if let key = key { + writeKey(key) + } + writeValue(url.absoluteString) + return true + case let decimal as Decimal: + if let key = key { + writeKey(key) + } + data.insert(contentsOf: decimal.description, at: &offset) + didWriteValue = true return true default: return false @@ -693,3 +718,11 @@ fileprivate struct UnkeyedJSONEncodingContainer: UnkeyedEncodingContainer { return _JSONEncoder(codingPath: codingPath, userInfo: self.encoder.userInfo, data: self.encoder.data) } } + +#if swift(<5.8) +extension UnsafeMutablePointer { + func update(from buffer: UnsafePointer, count: Int) { + self.assign(from: buffer, count: count) + } +} +#endif \ No newline at end of file diff --git a/Tests/IkigaJSONTests/JSONTests.swift b/Tests/IkigaJSONTests/JSONTests.swift index 0354b78..eebd57c 100644 --- a/Tests/IkigaJSONTests/JSONTests.swift +++ b/Tests/IkigaJSONTests/JSONTests.swift @@ -35,6 +35,32 @@ final class IkigaJSONTests: XCTestCase { XCTAssertNoThrow(try IkigaJSONDecoder().decode([UInt64].self, from: json)) } + func testURL() throws { + struct Info: Codable, Equatable { + let website: URL + } + + let domain = "https://unbeatable.software" + let info = Info(website: URL(string: domain)!) + let jsonObject = try IkigaJSONEncoder().encodeJSONObject(from: info) + XCTAssertEqual(jsonObject["website"].string, domain) + let info2 = try IkigaJSONDecoder().decode(Info.self, from: jsonObject.data) + XCTAssertEqual(info, info2) + } + + func testDecimal() throws { + struct Info: Codable, Equatable { + let secretNumber: Decimal + } + + let secretNumber: Decimal = 3.1414 + let info = Info(secretNumber: secretNumber) + let jsonObject = try IkigaJSONEncoder().encodeJSONObject(from: info) + XCTAssertEqualWithAccuracy(jsonObject["secretNumber"].double!, 3.1414, accuracy: 0.001) + let info2 = try IkigaJSONDecoder().decode(Info.self, from: jsonObject.data) + XCTAssertEqual(info, info2) + } + func testEscapedUnicode() throws { do { let json: Data = #"{"simple":"\u00DF", "complex": "\ud83d\udc69\u200d\ud83d\udc69"}"#.data(using: .utf8)!