Skip to content

Commit

Permalink
Fix decoding of arrays with optional elements (#48)
Browse files Browse the repository at this point in the history
Also move protocols for tricks with metatypes to new Metatypes file.

Fix formatting of long lines in related files.

* Fix decoding of arrays with optional elements
* Rename header comment in Metatypes file
* Fix formatting
  • Loading branch information
MaxDesiatov committed Dec 30, 2018
1 parent a4954c9 commit d4e7673
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 77 deletions.
36 changes: 36 additions & 0 deletions Sources/XMLCoder/Auxiliaries/Metatypes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Metatypes.swift
// XMLCoder
//
// Created by Max Desiatov on 30/12/2018.
//

/// Type-erased protocol helper for a metatype check in generic `decode`
/// overload.
protocol AnyEmptySequence {
init()
}

protocol AnyArray {
static var elementType: Any.Type { get }
}

extension Array: AnyEmptySequence, AnyArray {
static var elementType: Any.Type {
return Element.self
}
}

extension Dictionary: AnyEmptySequence {}

/// Type-erased protocol helper for a metatype check in generic `decode`
/// overload.
protocol AnyOptional {
init()
}

extension Optional: AnyOptional {
init() {
self = nil
}
}
17 changes: 13 additions & 4 deletions Sources/XMLCoder/Auxiliaries/XMLElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,11 @@ struct _XMLElement {
if let content = child.value {
switch elements[key] {
case let unkeyedBox as UnkeyedBox:
var boxes = unkeyedBox.unbox()
boxes.append(StringBox(content))
elements[key] = UnkeyedBox(boxes)
unkeyedBox.append(StringBox(content))
elements[key] = unkeyedBox
case let keyedBox as StringBox:
elements[key] = UnkeyedBox([keyedBox, StringBox(content)])
case _:
default:
elements[key] = StringBox(content)
}
} else if !child.elements.isEmpty || !child.attributes.isEmpty {
Expand All @@ -117,6 +116,16 @@ struct _XMLElement {
} else {
elements[key] = content
}
} else {
switch elements[key] {
case let unkeyedBox as UnkeyedBox:
unkeyedBox.append(NullBox())
elements[key] = unkeyedBox
case let keyedBox as StringBox:
elements[key] = UnkeyedBox([keyedBox, NullBox()])
default:
elements[key] = NullBox()
}
}
}
}
Expand Down
17 changes: 13 additions & 4 deletions Sources/XMLCoder/Auxiliaries/XMLStackParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ class _XMLStackParser: NSObject {
var root: _XMLElement?
private var stack: [_XMLElement] = []

static func parse(with data: Data, errorContextLength length: UInt) throws -> KeyedBox {
static func parse(with data: Data,
errorContextLength length: UInt) throws -> KeyedBox {
let parser = _XMLStackParser()

let node = try parser.parse(with: data, errorContextLength: length)

return node.flatten()
}

func parse(with data: Data, errorContextLength: UInt) throws -> _XMLElement {
func parse(with data: Data,
errorContextLength: UInt) throws -> _XMLElement {
let xmlParser = XMLParser(data: data)
xmlParser.delegate = self

Expand Down Expand Up @@ -79,12 +81,19 @@ extension _XMLStackParser: XMLParserDelegate {
stack = []
}

func parser(_: XMLParser, didStartElement elementName: String, namespaceURI _: String?, qualifiedName _: String?, attributes attributeDict: [String: String] = [:]) {
func parser(_: XMLParser,
didStartElement elementName: String,
namespaceURI _: String?,
qualifiedName _: String?,
attributes attributeDict: [String: String] = [:]) {
let element = _XMLElement(key: elementName, attributes: attributeDict)
stack.append(element)
}

func parser(_: XMLParser, didEndElement _: String, namespaceURI _: String?, qualifiedName _: String?) {
func parser(_: XMLParser,
didEndElement _: String,
namespaceURI _: String?,
qualifiedName _: String?) {
guard var element = stack.popLast() else {
return
}
Expand Down
6 changes: 4 additions & 2 deletions Sources/XMLCoder/Decoder/XMLDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,7 @@ extension _XMLDecoder {
func unbox<T: Decodable>(_ box: Box) throws -> T {
let decoded: T
let type = T.self

if type == Date.self || type == NSDate.self {
let date: Date = try unbox(box)
decoded = date as! T
Expand All @@ -620,9 +621,10 @@ extension _XMLDecoder {
decoded = decimal as! T
} else {
storage.push(container: box)
defer { storage.popContainer() }
return try type.init(from: self)
decoded = try type.init(from: self)
storage.popContainer()
}

return decoded
}
}
90 changes: 41 additions & 49 deletions Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@

import Foundation

/// Type-erased protocol helper for a metatype check in generic `decode`
/// overload.
private protocol AnyEmptySequence {
init()
}

extension Array: AnyEmptySequence {}
extension Dictionary: AnyEmptySequence {}

// MARK: Decoding Containers

struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol {
Expand All @@ -43,7 +34,8 @@ struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol
self.container = container
case .convertFromSnakeCase:
// Convert the snake case keys in the container to camel case.
// If we hit a duplicate key after conversion, then we'll use the first one we saw. Effectively an undefined behavior with dictionaries.
// If we hit a duplicate key after conversion, then we'll use the
// first one we saw. Effectively an undefined behavior with dictionaries.
let attributes = container.attributes.map { key, value in
(XMLDecoder.KeyDecodingStrategy._convertFromSnakeCase(key), value)
}
Expand All @@ -61,10 +53,14 @@ struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol
self.container = KeyedBox(elements: elements, attributes: attributes)
case let .custom(converter):
let attributes = container.attributes.map { key, value in
(converter(decoder.codingPath + [_XMLKey(stringValue: key, intValue: nil)]).stringValue, value)
(converter(decoder.codingPath +
[_XMLKey(stringValue: key, intValue: nil)]).stringValue,
value)
}
let elements = container.elements.map { key, value in
(converter(decoder.codingPath + [_XMLKey(stringValue: key, intValue: nil)]).stringValue, value)
(converter(decoder.codingPath +
[_XMLKey(stringValue: key, intValue: nil)]).stringValue,
value)
}
self.container = KeyedBox(elements: elements, attributes: attributes)
}
Expand Down Expand Up @@ -113,15 +109,11 @@ struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol
}

public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

public func decode(_ type: Decimal.Type, forKey key: Key) throws -> Decimal {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

public func decode(_ type: Int.Type, forKey key: Key) throws -> Int {
Expand Down Expand Up @@ -173,58 +165,50 @@ struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol
}

public func decode(_ type: String.Type, forKey key: Key) throws -> String {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

public func decode(_ type: Date.Type, forKey key: Key) throws -> Date {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

public func decode(_ type: Data.Type, forKey key: Key) throws -> Data {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

public func decode<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
let attributeNotFound = (container.attributes[key.stringValue] == nil)
let elementNotFound = (container.elements[key.stringValue] == nil)

if type is AnyEmptySequence.Type, attributeNotFound, elementNotFound {
return (type as! AnyEmptySequence.Type).init() as! T
if let type = type as? AnyEmptySequence.Type,
attributeNotFound, elementNotFound {
return type.init() as! T
}

return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
return try decodeConcrete(type, forKey: key)
}

private func decodeSignedInteger<T: BinaryInteger & SignedInteger & Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
private func decodeSignedInteger<T>(_ type: T.Type,
forKey key: Key) throws -> T
where T: BinaryInteger & SignedInteger & Decodable {
return try decodeConcrete(type, forKey: key)
}

private func decodeUnsignedInteger<T: BinaryInteger & UnsignedInteger & Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
private func decodeUnsignedInteger<T>(_ type: T.Type,
forKey key: Key) throws -> T
where T: BinaryInteger & UnsignedInteger & Decodable {
return try decodeConcrete(type, forKey: key)
}

private func decodeFloatingPoint<T: BinaryFloatingPoint & Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
return try decode(type, forKey: key) { decoder, box in
try decoder.unbox(box)
}
private func decodeFloatingPoint<T>(_ type: T.Type,
forKey key: Key) throws -> T
where T: BinaryFloatingPoint & Decodable {
return try decodeConcrete(type, forKey: key)
}

private func decode<T: Decodable>(
private func decodeConcrete<T: Decodable>(
_ type: T.Type,
forKey key: Key,
decode _: (_XMLDecoder, Box) throws -> T?
forKey key: Key
) throws -> T {
guard let entry = container.elements[key.stringValue] ?? container.attributes[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(
Expand All @@ -236,14 +220,22 @@ struct _XMLKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerProtocol
decoder.codingPath.append(key)
defer { decoder.codingPath.removeLast() }

guard let value: T = try decoder.unbox(entry) else {
let value: T? = try decoder.unbox(entry)

if value == nil,
let type = type as? AnyArray.Type,
type.elementType is AnyOptional.Type {
return [nil] as! T
}

guard let unwrapped = value else {
throw DecodingError.valueNotFound(type, DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected \(type) value but found null instead."
))
}

return value
return unwrapped
}

public func nestedContainer<NestedKey>(keyedBy _: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> {
Expand Down

0 comments on commit d4e7673

Please sign in to comment.