Skip to content

Commit 3378bf1

Browse files
committed
Foundation URL + Decimal support
1 parent 844b186 commit 3378bf1

File tree

3 files changed

+86
-11
lines changed

3 files changed

+86
-11
lines changed

Sources/IkigaJSON/Codable/JSONDecoder.swift

+16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import Foundation
22
import NIO
33

4+
enum JSONDecoderError: Error {
5+
case invalidURL(String)
6+
case invalidDecimal(String)
7+
}
8+
49
@available(OSX 10.12, iOS 10, *)
510
let isoFormatter = ISO8601DateFormatter()
611
let isoDateFormatter: DateFormatter = {
@@ -281,6 +286,17 @@ fileprivate struct _JSONDecoder: Decoder {
281286
@unknown default:
282287
throw JSONParserError.unknownJSONStrategy
283288
}
289+
case is URL.Type:
290+
let string = try singleValueContainer().decode(String.self)
291+
292+
guard let url = URL(string: string) else {
293+
throw JSONDecoderError.invalidURL(string)
294+
}
295+
296+
return url as! D
297+
case is Decimal.Type:
298+
let double = try singleValueContainer().decode(Double.self)
299+
return Decimal(floatLiteral: double) as! D
284300
default:
285301
break
286302
}

Sources/IkigaJSON/Codable/JSONEncoder.swift

+44-11
Original file line numberDiff line numberDiff line change
@@ -151,21 +151,21 @@ final class SharedEncoderData {
151151
/// Inserts the other autdeallocated storage into this storage
152152
func insert(contentsOf storage: SharedEncoderData, count: Int, at offset: inout Int) {
153153
beforeWrite(offset: offset, count: count)
154-
self.pointer.advanced(by: offset).assign(from: storage.pointer, count: count)
154+
self.pointer.advanced(by: offset).update(from: storage.pointer, count: count)
155155
offset = offset &+ count
156156
}
157157

158158
/// Inserts the other autdeallocated storage into this storage
159159
func insert(contentsOf data: [UInt8], at offset: inout Int) {
160160
beforeWrite(offset: offset, count: data.count)
161-
self.pointer.advanced(by: offset).assign(from: data, count: data.count)
161+
self.pointer.advanced(by: offset).update(from: data, count: data.count)
162162
offset = offset &+ data.count
163163
}
164164

165165
/// Inserts the other autdeallocated storage into this storage
166166
func insert(contentsOf storage: StaticString, at offset: inout Int) {
167167
beforeWrite(offset: offset, count: storage.utf8CodeUnitCount)
168-
self.pointer.advanced(by: offset).assign(from: storage.utf8Start, count: storage.utf8CodeUnitCount)
168+
self.pointer.advanced(by: offset).update(from: storage.utf8Start, count: storage.utf8CodeUnitCount)
169169
offset = offset &+ storage.utf8CodeUnitCount
170170
}
171171

@@ -175,7 +175,7 @@ final class SharedEncoderData {
175175
let utf8 = string.utf8
176176
let count = utf8.withContiguousStorageIfAvailable { utf8String -> Int in
177177
self.beforeWrite(offset: writeOffset, count: utf8String.count)
178-
self.pointer.advanced(by: writeOffset).assign(
178+
self.pointer.advanced(by: writeOffset).update(
179179
from: utf8String.baseAddress!,
180180
count: utf8String.count
181181
)
@@ -187,7 +187,7 @@ final class SharedEncoderData {
187187
} else {
188188
let count = utf8.count
189189
let buffer = Array(utf8)
190-
self.pointer.advanced(by: writeOffset).assign(
190+
self.pointer.advanced(by: writeOffset).update(
191191
from: buffer,
192192
count: count
193193
)
@@ -366,10 +366,14 @@ fileprivate final class _JSONEncoder: Encoder {
366366
case .deferredToDate:
367367
return false
368368
case .secondsSince1970:
369-
key.map { writeKey($0) }
369+
if let key = key {
370+
writeKey(key)
371+
}
370372
writeValue(date.timeIntervalSince1970)
371373
case .millisecondsSince1970:
372-
key.map { writeKey($0) }
374+
if let key = key {
375+
writeKey(key)
376+
}
373377
writeValue(date.timeIntervalSince1970 * 1000)
374378
case .iso8601:
375379
let string: String
@@ -384,11 +388,15 @@ fileprivate final class _JSONEncoder: Encoder {
384388
writeValue(string)
385389
case .formatted(let formatter):
386390
let string = formatter.string(from: date)
387-
key.map { writeKey($0) }
391+
if let key = key {
392+
writeKey(key)
393+
}
388394
writeValue(string)
389395
case .custom(let custom):
390396
let offsetBeforeKey = offset, hadWrittenValue = didWriteValue
391-
key.map { writeKey($0) }
397+
if let key = key {
398+
writeKey(key)
399+
}
392400
let encoder = _JSONEncoder(codingPath: codingPath, userInfo: userInfo, data: self.data)
393401
try custom(date, encoder)
394402
if encoder.didWriteValue {
@@ -409,11 +417,15 @@ fileprivate final class _JSONEncoder: Encoder {
409417
return false
410418
case .base64:
411419
let string = data.base64EncodedString()
412-
key.map { writeKey($0) }
420+
if let key = key {
421+
writeKey(key)
422+
}
413423
writeValue(string)
414424
case .custom(let custom):
415425
let offsetBeforeKey = offset, hadWrittenValue = didWriteValue
416-
key.map { writeKey($0) }
426+
if let key = key {
427+
writeKey(key)
428+
}
417429
let encoder = _JSONEncoder(codingPath: codingPath, userInfo: userInfo, data: self.data)
418430
try custom(data, encoder)
419431
if encoder.didWriteValue {
@@ -427,6 +439,19 @@ fileprivate final class _JSONEncoder: Encoder {
427439
throw JSONParserError.unknownJSONStrategy
428440
}
429441

442+
return true
443+
case let url as URL:
444+
if let key = key {
445+
writeKey(key)
446+
}
447+
writeValue(url.absoluteString)
448+
return true
449+
case let decimal as Decimal:
450+
if let key = key {
451+
writeKey(key)
452+
}
453+
data.insert(contentsOf: decimal.description, at: &offset)
454+
didWriteValue = true
430455
return true
431456
default:
432457
return false
@@ -693,3 +718,11 @@ fileprivate struct UnkeyedJSONEncodingContainer: UnkeyedEncodingContainer {
693718
return _JSONEncoder(codingPath: codingPath, userInfo: self.encoder.userInfo, data: self.encoder.data)
694719
}
695720
}
721+
722+
#if swift(<5.8)
723+
extension UnsafeMutablePointer {
724+
func update(from buffer: UnsafePointer<Pointee>, count: Int) {
725+
self.assign(from: buffer, count: count)
726+
}
727+
}
728+
#endif

Tests/IkigaJSONTests/JSONTests.swift

+26
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,32 @@ final class IkigaJSONTests: XCTestCase {
3535
XCTAssertNoThrow(try IkigaJSONDecoder().decode([UInt64].self, from: json))
3636
}
3737

38+
func testURL() throws {
39+
struct Info: Codable, Equatable {
40+
let website: URL
41+
}
42+
43+
let domain = "https://unbeatable.software"
44+
let info = Info(website: URL(string: domain)!)
45+
let jsonObject = try IkigaJSONEncoder().encodeJSONObject(from: info)
46+
XCTAssertEqual(jsonObject["website"].string, domain)
47+
let info2 = try IkigaJSONDecoder().decode(Info.self, from: jsonObject.data)
48+
XCTAssertEqual(info, info2)
49+
}
50+
51+
func testDecimal() throws {
52+
struct Info: Codable, Equatable {
53+
let secretNumber: Decimal
54+
}
55+
56+
let secretNumber: Decimal = 3.1414
57+
let info = Info(secretNumber: secretNumber)
58+
let jsonObject = try IkigaJSONEncoder().encodeJSONObject(from: info)
59+
XCTAssertEqualWithAccuracy(jsonObject["secretNumber"].double!, 3.1414, accuracy: 0.001)
60+
let info2 = try IkigaJSONDecoder().decode(Info.self, from: jsonObject.data)
61+
XCTAssertEqual(info, info2)
62+
}
63+
3864
func testEscapedUnicode() throws {
3965
do {
4066
let json: Data = #"{"simple":"\u00DF", "complex": "\ud83d\udc69\u200d\ud83d\udc69"}"#.data(using: .utf8)!

0 commit comments

Comments
 (0)