Skip to content
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
4 changes: 2 additions & 2 deletions Sources/KeyValueDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import CoreFoundation
import Foundation

/// Top level encoder that converts `[String: Any]`, `[Any]` or `Any` into `Codable` types.
public final class KeyValueDecoder {
public struct KeyValueDecoder: Sendable {

/// Contextual user-provided information for use during encoding.
public var userInfo: [CodingUserInfoKey: any Sendable]
Expand Down Expand Up @@ -94,7 +94,7 @@ extension KeyValueDecoder: TopLevelDecoder {
extension KeyValueDecoder {

static func makePlistCompatible() -> KeyValueDecoder {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.nilDecodingStrategy = .stringNull
return decoder
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/KeyValueEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import Foundation

/// Top level encoder that converts `Codable` instances into loosely typed `[String: Any]`, `[Any]` or `Any`.
public final class KeyValueEncoder {
public struct KeyValueEncoder: Sendable {

/// Contextual user-provided information for use during encoding.
public var userInfo: [CodingUserInfoKey: any Sendable]
Expand Down Expand Up @@ -85,7 +85,7 @@ extension KeyValueEncoder: TopLevelEncoder {
extension KeyValueEncoder {

static func makePlistCompatible() -> KeyValueEncoder {
let encoder = KeyValueEncoder()
var encoder = KeyValueEncoder()
encoder.nilEncodingStrategy = .stringNull
return encoder
}
Expand Down
18 changes: 9 additions & 9 deletions Tests/KeyValueDecoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ struct KeyValueDecoderTests {

@Test
func decodesRounded_Ints() throws {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero)

#expect(
Expand Down Expand Up @@ -235,7 +235,7 @@ struct KeyValueDecoderTests {

@Test
func decodesRounded_UInts() throws {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero)

#expect(
Expand Down Expand Up @@ -422,7 +422,7 @@ struct KeyValueDecoderTests {

@Test
func decodes_Null() throws {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.nilDecodingStrategy = .default

#expect(throws: (any Error).self) {
Expand Down Expand Up @@ -461,7 +461,7 @@ struct KeyValueDecoderTests {

@Test
func decodes_UnkeyedOptionals() throws {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()

decoder.nilDecodingStrategy = .removed
#expect(
Expand Down Expand Up @@ -704,7 +704,7 @@ struct KeyValueDecoderTests {

@Test
func decodes_UnkeyedNil() throws {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.nilDecodingStrategy = .default

#expect(
Expand Down Expand Up @@ -917,7 +917,7 @@ struct KeyValueDecoderTests {
UInt8(from: Double.nan, using: .clamping(roundingRule: nil)) == nil
)

let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.intDecodingStrategy = .clamping(roundingRule: .toNearestOrAwayFromZero)
#expect(
try decoder.decode([Int8].self, from: [10, 20.5, 1000, -Double.infinity]) == [
Expand Down Expand Up @@ -1004,7 +1004,7 @@ private extension KeyValueDecoder {
return try closure(&container)
}

let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.userInfo[.decoder] = proxy as any DecodingProxy
_ = try decoder.decode(StubDecoder.self, from: value)
return proxy.result!
Expand All @@ -1019,14 +1019,14 @@ private extension KeyValueDecoder {
return try closure(&container)
}

let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.userInfo[.decoder] = proxy as any DecodingProxy
_ = try decoder.decode(StubDecoder.self, from: value)
return proxy.result!
}

static func makeJSONCompatible() -> KeyValueDecoder {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.nilDecodingStrategy = .nsNull
return decoder
}
Expand Down
45 changes: 28 additions & 17 deletions Tests/KeyValueDecoderXCTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

import XCTest

final class KeyValueDecoderTests: XCTestCase {
final class KeyValueDecoderXCTests: XCTestCase {

func testDecodes_String() {
let decoder = KeyValueDecoder()
Expand Down Expand Up @@ -149,7 +149,7 @@ final class KeyValueDecoderTests: XCTestCase {
}

func testDecodesRounded_Ints() {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero)

XCTAssertEqual(
Expand Down Expand Up @@ -247,7 +247,7 @@ final class KeyValueDecoderTests: XCTestCase {
}

func testDecodesRounded_UInts() {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero)

XCTAssertEqual(
Expand Down Expand Up @@ -460,7 +460,7 @@ final class KeyValueDecoderTests: XCTestCase {
}

func testDecodes_Null() {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.nilDecodingStrategy = .default

XCTAssertThrowsError(
Expand Down Expand Up @@ -498,7 +498,7 @@ final class KeyValueDecoderTests: XCTestCase {
}

func testDecodes_UnkeyedOptionals() {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()

decoder.nilDecodingStrategy = .removed
XCTAssertEqual(
Expand Down Expand Up @@ -749,7 +749,7 @@ final class KeyValueDecoderTests: XCTestCase {
}

func testDecodes_UnkeyedNil() {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.nilDecodingStrategy = .default

XCTAssertEqual(
Expand Down Expand Up @@ -963,7 +963,7 @@ final class KeyValueDecoderTests: XCTestCase {
)

// [10, , 20.5, 1000, -Double.infinity]
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.intDecodingStrategy = .clamping(roundingRule: .toNearestOrAwayFromZero)
XCTAssertEqual(
try decoder.decode([Int8].self, from: [10, 20.5, 1000, -Double.infinity]),
Expand Down Expand Up @@ -1037,32 +1037,39 @@ private extension KeyValueDecoder {

}

static func decodeValue<K: CodingKey, T>(from value: [String: Any], keyedBy: K.Type = K.self, with closure: @escaping (inout KeyedDecodingContainer<K>) throws -> T) throws -> T {
static func decodeValue<K: CodingKey, T>(
from value: [String: Any],
keyedBy: K.Type = K.self,
with closure: @escaping (inout KeyedDecodingContainer<K>
) throws -> T) throws -> T {
let proxy = StubDecoder.Proxy { decoder in
var container = try decoder.container(keyedBy: K.self)
return try closure(&container)
}

let decoder = KeyValueDecoder()
decoder.userInfo[.decoder] = proxy.decode(from:)
var decoder = KeyValueDecoder()
decoder.userInfo[.decoder] = proxy as any DecodingProxy
_ = try decoder.decode(StubDecoder.self, from: value)
return proxy.result!
}

static func decodeUnkeyedValue<T>(from value: [Any], with closure: @escaping (inout any UnkeyedDecodingContainer) throws -> T) throws -> T {
static func decodeUnkeyedValue<T>(
from value: [Any],
with closure: @escaping (inout any UnkeyedDecodingContainer) throws -> T
) throws -> T {
let proxy = StubDecoder.Proxy { decoder in
var container = try decoder.unkeyedContainer()
return try closure(&container)
}

let decoder = KeyValueDecoder()
decoder.userInfo[.decoder] = proxy.decode(from:)
var decoder = KeyValueDecoder()
decoder.userInfo[.decoder] = proxy as any DecodingProxy
_ = try decoder.decode(StubDecoder.self, from: value)
return proxy.result!
}

static func makeJSONCompatible() -> KeyValueDecoder {
let decoder = KeyValueDecoder()
var decoder = KeyValueDecoder()
decoder.nilDecodingStrategy = .nsNull
return decoder
}
Expand All @@ -1072,9 +1079,13 @@ private extension CodingUserInfoKey {
static let decoder = CodingUserInfoKey(rawValue: "decoder")!
}

private protocol DecodingProxy: Sendable {
func decode(from decoder: any Decoder) throws
}

private struct StubDecoder: Decodable {

final class Proxy<T> {
final class Proxy<T>: @unchecked Sendable, DecodingProxy {
private let closure: (any Decoder) throws -> T
private(set) var result: T?

Expand All @@ -1088,8 +1099,8 @@ private struct StubDecoder: Decodable {
}

init(from decoder: any Decoder) throws {
let closure = decoder.userInfo[.decoder] as! (any Decoder) throws -> Void
try closure(decoder)
let proxy = decoder.userInfo[.decoder] as! any DecodingProxy
try proxy.decode(from: decoder)
}
}

Expand Down
19 changes: 10 additions & 9 deletions Tests/KeyValueEncoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ struct KeyValueEncodedTests {

@Test
func nilEncodingStrategy_SingleContainer() throws {
let encoder = KeyValueEncoder()
var encoder = KeyValueEncoder()

encoder.nilEncodingStrategy = .removed
#expect(
Expand Down Expand Up @@ -568,7 +568,7 @@ struct KeyValueEncodedTests {

@Test
func nilEncodingStrategy_UnkeyedContainer() throws {
let encoder = KeyValueEncoder()
var encoder = KeyValueEncoder()

encoder.nilEncodingStrategy = .removed
#expect(
Expand Down Expand Up @@ -677,7 +677,7 @@ private extension KeyValueEncoder {

static func encodeSingleValue(nilEncodingStrategy: NilEncodingStrategy = .default,
with closure: (inout any SingleValueEncodingContainer) throws -> Void) throws -> EncodedValue {
let encoder = KeyValueEncoder()
var encoder = KeyValueEncoder()
encoder.nilEncodingStrategy = nilEncodingStrategy
return try encoder.encodeValue {
var container = $0.singleValueContainer()
Expand All @@ -692,11 +692,12 @@ private extension KeyValueEncoder {
}
}

static func encodeKeyedValue<K: CodingKey>(keyedBy: K.Type = K.self,
nilEncodingStrategy: NilEncodingStrategy = .default,
with closure: @escaping (inout KeyedEncodingContainer<K>) throws -> Void) throws
-> EncodedValue {
let encoder = KeyValueEncoder()
static func encodeKeyedValue<K: CodingKey>(
keyedBy: K.Type = K.self,
nilEncodingStrategy: NilEncodingStrategy = .default,
with closure: @escaping (inout KeyedEncodingContainer<K>) throws -> Void
) throws -> EncodedValue {
var encoder = KeyValueEncoder()
encoder.nilEncodingStrategy = nilEncodingStrategy
return try encoder.encodeValue {
var container = $0.container(keyedBy: K.self)
Expand All @@ -711,7 +712,7 @@ private extension KeyValueEncoder {
}

static func makeJSONCompatible() -> KeyValueEncoder {
let encoder = KeyValueEncoder()
var encoder = KeyValueEncoder()
encoder.nilEncodingStrategy = .nsNull
return encoder
}
Expand Down
12 changes: 6 additions & 6 deletions Tests/KeyValueEncoderXCTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

import XCTest

final class KeyValueEncodedTests: XCTestCase {
final class KeyValueEncodedXCTests: XCTestCase {

typealias EncodedValue = KeyValueEncoder.EncodedValue

Expand Down Expand Up @@ -543,7 +543,7 @@ final class KeyValueEncodedTests: XCTestCase {
}

func testNilEncodingStrategy_SingleContainer() {
let encoder = KeyValueEncoder()
var encoder = KeyValueEncoder()

encoder.nilEncodingStrategy = .removed
XCTAssertNil(
Expand Down Expand Up @@ -572,7 +572,7 @@ final class KeyValueEncodedTests: XCTestCase {
}

func testNilEncodingStrategy_UnkeyedContainer() {
let encoder = KeyValueEncoder()
var encoder = KeyValueEncoder()

encoder.nilEncodingStrategy = .removed
XCTAssertEqual(
Expand Down Expand Up @@ -670,7 +670,7 @@ private extension KeyValueEncoder {

static func encodeSingleValue(nilEncodingStrategy: NilEncodingStrategy = .default,
with closure: (inout any SingleValueEncodingContainer) throws -> Void) throws -> EncodedValue {
let encoder = KeyValueEncoder()
var encoder = KeyValueEncoder()
encoder.nilEncodingStrategy = nilEncodingStrategy
return try encoder.encodeValue {
var container = $0.singleValueContainer()
Expand All @@ -688,7 +688,7 @@ private extension KeyValueEncoder {
static func encodeKeyedValue<K: CodingKey>(keyedBy: K.Type = K.self,
nilEncodingStrategy: NilEncodingStrategy = .default,
with closure: @escaping (inout KeyedEncodingContainer<K>) throws -> Void) throws -> EncodedValue {
let encoder = KeyValueEncoder()
var encoder = KeyValueEncoder()
encoder.nilEncodingStrategy = nilEncodingStrategy
return try encoder.encodeValue {
var container = $0.container(keyedBy: K.self)
Expand All @@ -703,7 +703,7 @@ private extension KeyValueEncoder {
}

static func makeJSONCompatible() -> KeyValueEncoder {
let encoder = KeyValueEncoder()
var encoder = KeyValueEncoder()
encoder.nilEncodingStrategy = .nsNull
return encoder
}
Expand Down