|
import Foundation |
|
|
|
typealias AnyDictionary = [String: Any] |
|
typealias AnyArray = [Any] |
|
|
|
enum PathSeparator { |
|
case character(Character) |
|
case custom((String) -> [String]) |
|
|
|
func separate(path: String) -> [String] { |
|
let keys: [String] |
|
switch self { |
|
case .character(let character): |
|
keys = path.split(separator: character).map { "\($0)" } |
|
case .custom(let closure): |
|
keys = closure(path) |
|
} |
|
return keys |
|
} |
|
} |
|
|
|
struct PathReader { |
|
let payload: Any |
|
|
|
func value<T>(at path: String, pathSeparator: PathSeparator = .character(".")) -> T? { |
|
var keys = pathSeparator.separate(path: path) |
|
guard let valueKey = keys.popLast() else { |
|
return nil |
|
} |
|
|
|
var current: Any? = payload |
|
while current != nil && !keys.isEmpty { |
|
guard let payload = current else { |
|
break |
|
} |
|
|
|
if let key = keys.first { |
|
keys.remove(at: 0) |
|
current = getValue(key: key, payload: payload) |
|
} |
|
} |
|
|
|
if let payload = current, |
|
let value = getValue(key: valueKey, payload: payload) as? T { |
|
return value |
|
} else { |
|
return nil |
|
} |
|
} |
|
|
|
private func getValue(key: String, payload: Any) -> Any? { |
|
let result: Any? |
|
if let array = payload as? AnyArray, |
|
let index = Int(key), |
|
index < array.count { |
|
result = array[index] |
|
} else if let dictionary = payload as? AnyDictionary { |
|
result = dictionary[key] |
|
} else { |
|
result = nil |
|
} |
|
return result |
|
} |
|
} |
|
|
|
extension Bundle { |
|
func value<T>(at path: String, pathSeparator: PathSeparator = .character(".")) -> T? { |
|
guard let infoDictionary = Bundle.main.infoDictionary else { |
|
return nil |
|
} |
|
let reader = PathReader(payload: infoDictionary) |
|
if let value: T = reader.value(at: path, pathSeparator: pathSeparator) { |
|
return value |
|
} else { |
|
return nil |
|
} |
|
} |
|
|
|
} |
|
|
|
extension Dictionary { |
|
func value<T>(at path: String, pathSeparator: PathSeparator = .character(".")) -> T? { |
|
let reader = PathReader(payload: self) |
|
if let value: T = reader.value(at: path, pathSeparator: pathSeparator) { |
|
return value |
|
} else { |
|
return nil |
|
} |
|
} |
|
} |
|
|
|
extension Array { |
|
func value<T>(at path: String, pathSeparator: PathSeparator = .character(".")) -> T? { |
|
let reader = PathReader(payload: self) |
|
if let value: T = reader.value(at: path, pathSeparator: pathSeparator) { |
|
return value |
|
} else { |
|
return nil |
|
} |
|
} |
|
} |
|
|
|
//: --- |
|
|
|
let oneDictionary: AnyDictionary = [ |
|
"One": 1 |
|
] |
|
|
|
if let number: Int = oneDictionary.value(at: "One") { |
|
print("Value is \(number)") |
|
} else { |
|
print("Value not found") |
|
} |
|
|
|
print("---") |
|
|
|
let deeperDictionary: AnyDictionary = [ |
|
"A": [ |
|
"B": [ |
|
"C": "ABC" |
|
] |
|
] |
|
] |
|
|
|
if let string: String = deeperDictionary.value(at: "A.B.C") { |
|
print("Value is \(string)") |
|
} else { |
|
print("Value not found") |
|
} |
|
|
|
print("Custom Separator:") |
|
let customSeparator: (String) -> [String] = { |
|
$0.split(separator: "_").map { "\($0)" } |
|
} |
|
if let string: String = deeperDictionary.value(at: "A_B_C", pathSeparator: .custom(customSeparator)) { |
|
print("Value is \(string)") |
|
} else { |
|
print("Value not found") |
|
} |
|
|
|
print("---") |
|
|
|
// include an array |
|
let mixedDictionary: AnyDictionary = [ |
|
"A": [ |
|
"B": [ |
|
["C": "ABC"], |
|
["D": "DEF"] |
|
] |
|
] |
|
] |
|
|
|
if let array: AnyArray = mixedDictionary.value(at: "A.B") { |
|
print("Value is \(array)") |
|
} else { |
|
print("Value not found") |
|
} |
|
|
|
if let cString: String = mixedDictionary.value(at: "A.B.0.C") { |
|
print("Value is \(cString)") |
|
} else { |
|
print("Value not found") |
|
} |
|
|
|
if let dString: String = mixedDictionary.value(at: "A.B.1.D") { |
|
print("Value is \(dString)") |
|
} else { |
|
print("Value not found") |
|
} |
|
|
|
print("---") |
|
|
|
let nestedArray = [ |
|
[1, 2, 3], |
|
[4, 5, 6] |
|
] |
|
|
|
if let nestedNumber: Int = nestedArray.value(at: "0.2") { |
|
print("Value is \(nestedNumber)") |
|
} |
|
else { |
|
print("Value not found") |
|
} |
|
|
|
if let firstArray: [Int] = nestedArray.value(at: "0") { |
|
print("Value is \(firstArray)") |
|
} |
|
else { |
|
print("Value not found") |
|
} |
|
|
|
if let secondArray: [Int] = nestedArray.value(at: "1") { |
|
print("Value is \(secondArray)") |
|
} |
|
else { |
|
print("Value not found") |
|
} |
|
|
|
enum InfoDictionary { |
|
enum MyApp { |
|
private static var infoDictionary: [String: Any]? { |
|
// Use Bundle.main.infoDictionary in real app |
|
[ |
|
"MyApp": [ |
|
"Scheme": "https://", |
|
"Host": "api.myhost.com", |
|
"Auth": "AES256", |
|
"Locales": [ |
|
"en-US", |
|
"es-MX" |
|
] |
|
] |
|
] |
|
} |
|
static var Scheme: String? { |
|
infoDictionary?.value(at: "MyApp.Scheme") |
|
} |
|
static var Host: String? { |
|
infoDictionary?.value(at: "MyApp.Host") |
|
} |
|
static var Auth: String? { |
|
infoDictionary?.value(at: "MyApp.Auth") |
|
} |
|
static var Locales: [String]? { |
|
infoDictionary?.value(at: "MyApp.Locales") |
|
} |
|
} |
|
} |
|
|
|
InfoDictionary.MyApp.Scheme |
|
InfoDictionary.MyApp.Host |
|
InfoDictionary.MyApp.Auth |
|
InfoDictionary.MyApp.Locales |