Skip to content

Commit

Permalink
Merge pull request JohnSundell#75 from plain-vanilla-games/add-suppor…
Browse files Browse the repository at this point in the history
…t-for-dicts-of-list-of-unboxables

Enable support for unboxing [K: [Unboxable]]
  • Loading branch information
JohnSundell authored Jun 18, 2016
2 parents 1079f44 + 65061ce commit 3a8664b
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 6 deletions.
40 changes: 34 additions & 6 deletions Sources/Unbox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -526,16 +526,44 @@ public class Unboxer {
return UnboxValueResolver<[String : V]>(self).resolveDictionaryValuesForKey(key, isKeyPath: isKeyPath, required: false, allowInvalidElements: false, valueTransform: { $0 })
}

/// Unbox a required Dictionary containing collections
public func unbox<K: UnboxableKey, V: CollectionType>(key: String, isKeyPath: Bool = true) -> [K : V] {
/// Unbox a required Dictionary containing dictionaries
public func unbox<K: UnboxableKey, V where V: CollectionType, V: DictionaryLiteralConvertible, V.Key: Hashable, V.Generator == DictionaryGenerator<V.Key, V.Value>>(key: String, isKeyPath: Bool = true) -> [K : V] {
return UnboxValueResolver<[String : V]>(self).resolveDictionaryValuesForKey(key, isKeyPath: isKeyPath, required: true, allowInvalidElements: false, valueTransform: { $0 }) ?? [:]
}
/// Unbox an optional Dictionary containing collections
public func unbox<K: UnboxableKey, V: CollectionType>(key: String, isKeyPath: Bool = true) -> [K : V]? {

/// Unbox an optional Dictionary containing dictionaries
public func unbox<K: UnboxableKey, V where V: CollectionType, V: DictionaryLiteralConvertible, V.Key: Hashable, V.Generator == DictionaryGenerator<V.Key, V.Value>>(key: String, isKeyPath: Bool = true) -> [K : V]? {
return UnboxValueResolver<[String : V]>(self).resolveDictionaryValuesForKey(key, isKeyPath: isKeyPath, required: false, allowInvalidElements: false, valueTransform: { $0 })
}

/// Unbox a required Dictionary containing array of simple values
public func unbox<K: UnboxableKey, V: UnboxableRawType>(key: String, isKeyPath: Bool = true, allowInvalidElements: Bool = false) -> [K : [V]] {
return UnboxValueResolver<[String: [V]]>(self).resolveDictionaryValuesForKey(key, isKeyPath: isKeyPath, required: true, allowInvalidElements: allowInvalidElements) {
return $0
} ?? [K: [V]]()
}

/// Unbox an optional Dictionary containing array of simple values
public func unbox<K: UnboxableKey, V: UnboxableRawType>(key: String, isKeyPath: Bool = true, allowInvalidElements: Bool = false) -> [K : [V]]? {
return UnboxValueResolver<[String: [V]]>(self).resolveDictionaryValuesForKey(key, isKeyPath: isKeyPath, required: false, allowInvalidElements: allowInvalidElements) {
return $0
}
}

/// Unbox a required Dictionary containing array of Unboxables
public func unbox<K: UnboxableKey, V: Unboxable>(key: String, isKeyPath: Bool = true, allowInvalidElements: Bool = false) -> [K : [V]] {
return UnboxValueResolver<[String: [UnboxableDictionary]]>(self).resolveDictionaryValuesForKey(key, isKeyPath: isKeyPath, required: true, allowInvalidElements: allowInvalidElements) {
return try? Unbox($0, context: self.context, allowInvalidElements: allowInvalidElements)
} ?? [K: [V]]()
}

/// Unbox an optional Dictionary containing array of Unboxables
public func unbox<K: UnboxableKey, V: Unboxable>(key: String, isKeyPath: Bool = true, allowInvalidElements: Bool = false) -> [K : [V]]? {
return UnboxValueResolver<[String: [UnboxableDictionary]]>(self).resolveDictionaryValuesForKey(key, isKeyPath: isKeyPath, required: false, allowInvalidElements: allowInvalidElements) {
return try? Unbox($0, context: self.context, allowInvalidElements: allowInvalidElements)
}
}

/// Unbox a required enum value
public func unbox<T: UnboxableEnum>(key: String, isKeyPath: Bool = true) -> T {
return UnboxValueResolver<T.RawValue>(self).resolveRequiredValueForKey(key, isKeyPath: isKeyPath, fallbackValue: T.unboxFallbackValue(), transform: {
Expand Down Expand Up @@ -772,7 +800,7 @@ extension UnboxValueResolver where T: CollectionType, T: DictionaryLiteralConver
self.unboxer.failForInvalidValue(self.unboxer.dictionary[key], forKey: key)
}

return [:]
return nil
}
}

Expand Down
158 changes: 158 additions & 0 deletions Tests/UnboxTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,138 @@ class UnboxTests: XCTestCase {
}
}

func testCustomDictionaryKeyTypeWithArrayOfUnboxables() {
struct Model: Unboxable {
let requiredModelDictionary: [UnboxTestDictionaryKey : [UnboxTestSimpleMock]]
let optionalModelDictionary: [UnboxTestDictionaryKey : [UnboxTestSimpleMock]]?

init(unboxer: Unboxer) {
self.requiredModelDictionary = unboxer.unbox("requiredModelDictionary")
self.optionalModelDictionary = unboxer.unbox("optionalModelDictionary")
}
}

do {
let unboxed: Model = try Unbox([
"requiredModelDictionary" : [
"key" : [
["int" : 31]
]
],
"optionalModelDictionary" : [
"optionalKey" : [
["int" : 19]
]
]
])

if let values = unboxed.requiredModelDictionary[UnboxTestDictionaryKey(key: "key")] {
XCTAssertEqual(values, [UnboxTestSimpleMock(int: 31)])
} else {
XCTFail("Key was missing from unboxed dictionary")
}

if let values = unboxed.optionalModelDictionary?[UnboxTestDictionaryKey(key: "optionalKey")] {
XCTAssertEqual(values, [UnboxTestSimpleMock(int: 19)])
} else {
XCTFail("Key was missing from unboxed dictionary")
}

let unboxedWithoutOptionals: Model = try Unbox([
"requiredModelDictionary" : [
"key" : [
["int" : 31]
]
]
])

if let values = unboxedWithoutOptionals.requiredModelDictionary[UnboxTestDictionaryKey(key: "key")] {
XCTAssertEqual(values, [UnboxTestSimpleMock(int: 31)])
} else {
XCTFail("Key was missing from unboxed dictionary")
}
XCTAssertNil(unboxedWithoutOptionals.optionalModelDictionary)
} catch {
XCTFail("\(error)")
}
}

func testCustomDictionaryKeyTypeWithArrayOfUnboxablesThrowsOnInvalidData() {
struct Model: Unboxable {
let requiredModelDictionary: [UnboxTestDictionaryKey : [UnboxTestSimpleMock]]

init(unboxer: Unboxer) {
self.requiredModelDictionary = unboxer.unbox("requiredModelDictionary")
}
}

do {
let _ : Model = try Unbox([
"requiredModelDictionary" : [
"key" : [
["int" : "asdf"]
]
],
])

XCTFail("Should throw error when unboxing on invalid data")
} catch {
// Test passed
}
}

func testCustomDictionaryKeyTypeWithArrayOfUnboxablesCanAllowInvalidData() {
struct Model: Unboxable {
let requiredModelDictionary: [UnboxTestDictionaryKey : [UnboxTestSimpleMock]]

init(unboxer: Unboxer) {
self.requiredModelDictionary = unboxer.unbox("requiredModelDictionary", allowInvalidElements:true)
}
}

do {
let unboxed : Model = try Unbox([
"requiredModelDictionary" : [
"key" : [
["int" : "asdf"]
]
],
])

if let values = unboxed.requiredModelDictionary[UnboxTestDictionaryKey(key: "key")] {
XCTAssertEqual(values, [])
} else {
XCTFail("Key was missing from unboxed dictionary")
}
} catch {
XCTFail("Should not throw error when unboxing on invalid data")
}
}

func testOptionalCustomDictionaryKeyTypeWithArrayOfUnboxablesDoesNotFail() {
struct Model: Unboxable {
let optionalModelDictionary: [UnboxTestDictionaryKey : [UnboxTestSimpleMock]]?

init(unboxer: Unboxer) {
self.optionalModelDictionary = unboxer.unbox("optionalModelDictionary")
}
}

do {
let unboxed : Model = try Unbox([
"requiredModelDictionary" : [
"key" : [
["int" : "asdf"]
]
],
])

XCTAssertNil(unboxed.optionalModelDictionary)
} catch {
XCTFail("Should not throw error when unboxing on invalid data")
}
}

func testWithInvalidRequiredUnboxable() {
var invalidDictionary = UnboxTestDictionaryWithAllRequiredKeysWithValidValues(false)
invalidDictionary[UnboxTestMock.requiredUnboxableKey] = "Totally not unboxable"
Expand Down Expand Up @@ -607,6 +739,32 @@ class UnboxTests: XCTestCase {
}
}

func testNestedArrayAsValueOfDictionary() {
struct Model: Unboxable {
let dictionaries: [String : [Int]]

init(unboxer: Unboxer) {
self.dictionaries = unboxer.unbox("dictionaries")
}
}

let dictionary: UnboxableDictionary = [
"dictionaries" : [
"one" : [1, 2],
"two" : [3, 4]
]
]

do {
let unboxed: Model = try Unbox(dictionary)
XCTAssertEqual(unboxed.dictionaries.count, 2)
XCTAssertEqual(unboxed.dictionaries["one"]!, [1, 2])
XCTAssertEqual(unboxed.dictionaries["two"]!, [3, 4])
} catch {
XCTFail("\(error)")
}
}

func testThrowingForMissingRequiredValues() {
let invalidDictionary: UnboxableDictionary = [:]

Expand Down

0 comments on commit 3a8664b

Please sign in to comment.