Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Foundation.URL and Foundation.Decimal #35

Merged
merged 1 commit into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading