Skip to content
Open
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: 0 additions & 4 deletions Sources/Decoder/DictionaryComponentDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,6 @@ extension DictionaryComponentDecoder {
return Date(timeIntervalSince1970: try decodePrimitiveValue(from: component, at: codingPath) / 1000.0)

case .iso8601:
guard #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) else {
fatalError("ISO8601DateFormatter is unavailable on this platform.")
}

let formattedDate = try decodePrimitiveValue(of: String.self, from: component, at: codingPath)

guard let date = ISO8601DateFormatter().date(from: formattedDate) else {
Expand Down
26 changes: 9 additions & 17 deletions Sources/Decoder/DictionaryDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,41 +54,33 @@ public final class DictionaryDecoder: Sendable {

// MARK: - Instance Methods

public func decode<T: Decodable>(
_ type: T.Type = T.self,
from dictionary: [String: Any]
) throws -> T {
private func rootDecoder(for dictionary: [String: Any]) -> DictionarySingleValueDecodingContainer {
let options = optionsMutex.withLock { $0 }

let decoder = DictionarySingleValueDecodingContainer(
return DictionarySingleValueDecodingContainer(
component: dictionary,
options: options,
userInfo: userInfo,
codingPath: []
)
}

return try T(from: decoder)
public func decode<T: Decodable>(
_ type: T.Type = T.self,
from dictionary: [String: Any]
) throws -> T {
try T(from: rootDecoder(for: dictionary))
}

public func decode<T: Decodable>(from dictionary: [String: Any]) throws -> T {
try decode(T.self, from: dictionary)
}

@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
public func decode<T: DecodableWithConfiguration>(
_ type: T.Type = T.self,
from dictionary: [String: Any],
configuration: T.DecodingConfiguration
) throws -> T {
let options = optionsMutex.withLock { $0 }

let decoder = DictionarySingleValueDecodingContainer(
component: dictionary,
options: options,
userInfo: userInfo,
codingPath: []
)

return try T(from: decoder, configuration: configuration)
try T(from: rootDecoder(for: dictionary), configuration: configuration)
}
}
12 changes: 5 additions & 7 deletions Sources/Decoder/DictionaryKeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

internal class DictionaryKeyedDecodingContainer<Key: CodingKey>:
KeyedDecodingContainerProtocol,
DictionaryComponentDecoder {
Expand Down Expand Up @@ -27,12 +25,12 @@ internal class DictionaryKeyedDecodingContainer<Key: CodingKey>:
case .useDefaultKeys:
self.components = components

case let.custom(closure):
let componentKeysAndValues = components.map { key, value in
(closure(codingPath.appending(AnyCodingKey(key))).stringValue, value)
}
case let .custom(closure):
let componentKeysAndValues = components
.sorted { $0.key < $1.key }
.map { key, value in (closure(codingPath.appending(AnyCodingKey(key))).stringValue, value) }

self.components = Dictionary(componentKeysAndValues) { $1 }
self.components = Dictionary(componentKeysAndValues) { first, _ in first }
}

self.options = options
Expand Down
2 changes: 0 additions & 2 deletions Sources/Decoder/DictionarySingleValueDecodingContainer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

internal final class DictionarySingleValueDecodingContainer:
Decoder,
SingleValueDecodingContainer,
Expand Down
78 changes: 47 additions & 31 deletions Sources/Decoder/DictionaryUnkeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

internal final class DictionaryUnkeyedDecodingContainer:
UnkeyedDecodingContainer,
DictionaryComponentDecoder {
Expand Down Expand Up @@ -41,7 +39,8 @@ internal final class DictionaryUnkeyedDecodingContainer:

// MARK: - Instance Methods

private func popNextComponent() throws -> Any? {
@inline(__always)
private func peekNextComponent() throws -> Any? {
guard currentIndex < components.count else {
let errorContext = DecodingError.Context(
codingPath: currentCodingPath,
Expand All @@ -51,95 +50,112 @@ internal final class DictionaryUnkeyedDecodingContainer:
throw DecodingError.valueNotFound(Any.self, errorContext)
}

defer {
currentIndex += 1
}

return components[currentIndex]
}

@inline(__always)
private func decodeNextComponent<T>(
_ decodeComponent: (_ component: Any?, _ codingPath: [CodingKey]) throws -> T
) throws -> T {
let value = try decodeComponent(try peekNextComponent(), currentCodingPath)

currentIndex += 1

return value
}

private func superDecoder(for component: Any?, at codingPath: [CodingKey]) -> Decoder {
DictionarySingleValueDecodingContainer(
component: component,
options: options,
userInfo: userInfo,
codingPath: codingPath
)
}

// MARK: - UnkeyedDecodingContainer

internal func decodeNil() throws -> Bool {
decodeNilComponent(from: try popNextComponent())
guard decodeNilComponent(from: try peekNextComponent()) else {
return false
}

currentIndex += 1

return true
}

internal func decode(_ type: Bool.Type) throws -> Bool {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: Int.Type) throws -> Int {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: Int8.Type) throws -> Int8 {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: Int16.Type) throws -> Int16 {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: Int32.Type) throws -> Int32 {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: Int64.Type) throws -> Int64 {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: UInt.Type) throws -> UInt {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: UInt8.Type) throws -> UInt8 {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: UInt16.Type) throws -> UInt16 {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: UInt32.Type) throws -> UInt32 {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: UInt64.Type) throws -> UInt64 {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: Double.Type) throws -> Double {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: Float.Type) throws -> Float {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode(_ type: String.Type) throws -> String {
try decodeComponentValue(from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(from: $0, at: $1) }
}

internal func decode<T: Decodable>(_ type: T.Type) throws -> T {
try decodeComponentValue(of: type, from: try popNextComponent(), at: currentCodingPath)
try decodeNextComponent { try decodeComponentValue(of: type, from: $0, at: $1) }
}

internal func nestedContainer<NestedKey: CodingKey>(
keyedBy keyType: NestedKey.Type
) throws -> KeyedDecodingContainer<NestedKey> {
try superDecoder().container(keyedBy: keyType)
try decodeNextComponent { try superDecoder(for: $0, at: $1).container(keyedBy: keyType) }
}

internal func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
try superDecoder().unkeyedContainer()
try decodeNextComponent { try superDecoder(for: $0, at: $1).unkeyedContainer() }
}

internal func superDecoder() throws -> Decoder {
DictionarySingleValueDecodingContainer(
component: try popNextComponent(),
options: options,
userInfo: userInfo,
codingPath: currentCodingPath
)
try decodeNextComponent { superDecoder(for: $0, at: $1) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ public enum DictionaryDateDecodingStrategy: Sendable {
case millisecondsSince1970

/// The strategy that formats dates according to the ISO 8601 standard.
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
case iso8601

/// The strategy that defers formatting settings to a supplied date formatter.
Expand Down
2 changes: 0 additions & 2 deletions Sources/Decoder/Options/DictionaryDecodingOptions.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

internal struct DictionaryDecodingOptions {

// MARK: - Instance Properties
Expand Down
5 changes: 3 additions & 2 deletions Sources/Decoder/Options/DictionaryKeyDecodingStrategy.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

/// The values that determine how to decode a type’s coding keys from Dictionary keys.
public enum DictionaryKeyDecodingStrategy: Sendable {

Expand All @@ -9,5 +7,8 @@ public enum DictionaryKeyDecodingStrategy: Sendable {
case useDefaultKeys

/// A key decoding strategy defined by the closure you supply.
///
/// If several keys are converted to the same key,
/// the value of the lexicographically smallest original key is used.
case custom(@Sendable (_ codingPath: [CodingKey]) -> CodingKey)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

/// The strategies for encoding nonconforming floating-point numbers,
/// also known as IEEE 754 exceptional values.
public enum DictionaryNonConformingFloatDecodingStrategy: Sendable {
Expand Down
2 changes: 0 additions & 2 deletions Sources/Encoder/DictionaryAnyKeyedEncodingContainer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

internal final class DictionaryAnyKeyedEncodingContainer: DictionaryComponentContainer {

// MARK: - Instance Properties
Expand Down
2 changes: 0 additions & 2 deletions Sources/Encoder/DictionaryComponent.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

internal indirect enum DictionaryComponent {

// MARK: - Enumeration Cases
Expand Down
2 changes: 0 additions & 2 deletions Sources/Encoder/DictionaryComponentContainer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

internal protocol DictionaryComponentContainer {

// MARK: - Instance Methods
Expand Down
6 changes: 1 addition & 5 deletions Sources/Encoder/DictionaryComponentEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,6 @@ extension DictionaryComponentEncoder {
return encodePrimitiveValue(date.timeIntervalSince1970, at: codingPath)

case .iso8601:
guard #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) else {
fatalError("ISO8601DateFormatter is unavailable on this platform.")
}

let formattedDate = ISO8601DateFormatter.string(
from: date,
timeZone: .iso8601TimeZone,
Expand Down Expand Up @@ -250,6 +246,6 @@ extension EncodingError {
Use DictionaryNonConformingFloatEncodingStrategy.convertToString to specify how the value should be encoded.
"""

return .invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: debugDescription))
return .invalidValue(value, Context(codingPath: codingPath, debugDescription: debugDescription))
}
}
35 changes: 13 additions & 22 deletions Sources/Encoder/DictionaryEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ public final class DictionaryEncoder: Sendable {

// MARK: - Instance Methods

public func encode<T: Encodable>(_ value: T) throws -> [String: Sendable] {
private func encodeRootValue<T>(
_ value: T,
encoding: (_ encoder: Encoder) throws -> Void
) throws -> [String: Sendable] {
let options = optionsMutex.withLock { $0 }

let encoder = DictionarySingleValueEncodingContainer(
Expand All @@ -70,7 +73,7 @@ public final class DictionaryEncoder: Sendable {
codingPath: []
)

try value.encode(to: encoder)
try encoding(encoder)

guard let dictionary = encoder.resolveValue() as? [String: Sendable] else {
let errorContext = EncodingError.Context(
Expand All @@ -84,30 +87,18 @@ public final class DictionaryEncoder: Sendable {
return dictionary
}

@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
public func encode<T: Encodable>(_ value: T) throws -> [String: Sendable] {
try encodeRootValue(value) { encoder in
try value.encode(to: encoder)
}
}

public func encode<T: EncodableWithConfiguration>(
_ value: T,
configuration: T.EncodingConfiguration
) throws -> [String: Sendable] {
let options = optionsMutex.withLock { $0 }

let encoder = DictionarySingleValueEncodingContainer(
options: options,
userInfo: userInfo,
codingPath: []
)

try value.encode(to: encoder, configuration: configuration)

guard let dictionary = encoder.resolveValue() as? [String: Sendable] else {
let errorContext = EncodingError.Context(
codingPath: [],
debugDescription: "Root component cannot be encoded in Dictionary"
)

throw EncodingError.invalidValue(value, errorContext)
try encodeRootValue(value) { encoder in
try value.encode(to: encoder, configuration: configuration)
}

return dictionary
}
}
2 changes: 0 additions & 2 deletions Sources/Encoder/DictionaryKeyedEncodingContainer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

internal final class DictionaryKeyedEncodingContainer<Key: CodingKey>:
KeyedEncodingContainerProtocol,
DictionaryComponentEncoder {
Expand Down
2 changes: 0 additions & 2 deletions Sources/Encoder/DictionarySingleValueEncodingContainer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

internal final class DictionarySingleValueEncodingContainer:
Encoder,
SingleValueEncodingContainer,
Expand Down
2 changes: 0 additions & 2 deletions Sources/Encoder/DictionaryUnkeyedEncodingContainer.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

internal final class DictionaryUnkeyedEncodingContainer:
UnkeyedEncodingContainer,
DictionaryComponentContainer,
Expand Down
Loading
Loading