Skip to content

Commit

Permalink
Merge pull request #35 from orlandos-nl/feature/foundation-url-decimal
Browse files Browse the repository at this point in the history
Support Foundation.URL and Foundation.Decimal
  • Loading branch information
Joannis authored Aug 23, 2023
2 parents 844b186 + 3378bf1 commit 338c161
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 11 deletions.
16 changes: 16 additions & 0 deletions Sources/IkigaJSON/Codable/JSONDecoder.swift
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -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
}
Expand Down
55 changes: 44 additions & 11 deletions Sources/IkigaJSON/Codable/JSONEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
)
Expand All @@ -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
)
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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<Pointee>, count: Int) {
self.assign(from: buffer, count: count)
}
}
#endif
26 changes: 26 additions & 0 deletions Tests/IkigaJSONTests/JSONTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)!
Expand Down

0 comments on commit 338c161

Please sign in to comment.