diff --git a/MVMCore/MVMCore/Categories/Dictionary+MFConvenience.swift b/MVMCore/MVMCore/Categories/Dictionary+MFConvenience.swift index 8e11396..efef7d3 100644 --- a/MVMCore/MVMCore/Categories/Dictionary+MFConvenience.swift +++ b/MVMCore/MVMCore/Categories/Dictionary+MFConvenience.swift @@ -192,6 +192,61 @@ public extension Dictionary { return previousObject } + + /// Transforms dictionary keys without modifying values. + /// Deduplicates transformed keys. + /// + /// Example: + /// ``` + /// ["one": 1, "two": 2, "three": 3, "": 4].mapKeys({ $0.first }, uniquingKeysWith: { max($0, $1) }) + /// // [Optional("o"): 1, Optional("t"): 3, nil: 4] + /// ``` + /// + /// - Parameters: + /// - transform: A closure that accepts each key of the dictionary as + /// its parameter and returns a transformed key of the same or of a different type. + /// - combine:A closure that is called with the values for any duplicate + /// keys that are encountered. The closure returns the desired value for + /// the final dictionary. + /// - Returns: A dictionary containing the transformed keys and values of this dictionary. + func mapKeys(_ transform: (Key) throws -> T, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows -> [T: Value] { + try .init(map { (try transform($0.key), $0.value) }, uniquingKeysWith: combine) + } + + /// Transforms dictionary keys without modifying values. + /// Drops (key, value) pairs where the transform results in a nil key. + /// Deduplicates transformed keys. + /// + /// Example: + /// ``` + /// ["one": 1, "two": 2, "three": 3, "": 4].compactMapKeys({ $0.first }, uniquingKeysWith: { max($0, $1) }) + /// // ["o": 1, "t": 3] + /// ``` + /// + /// - Parameters: + /// - transform: A closure that accepts each key of the dictionary as + /// its parameter and returns an optional transformed key of the same or of a different type. + /// - combine: A closure that is called with the values for any duplicate + /// keys that are encountered. The closure returns the desired value for + /// the final dictionary. + /// - Returns: A dictionary containing the non-nil transformed keys and values of this dictionary. + func compactMapKeys(_ transform: (Key) throws -> T?, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows -> [T: Value] { + try .init(compactMap { (try transform($0.key), $0.value) as? (T, Value) }, uniquingKeysWith: combine) + } + + /// Unconditionally merge another dictionary into this one with preference to the incoming keys. + func mergingLeft(_ other: Self) -> Self { + return merging(other) { first, second in + second + } + } + + /// Unconditionally merge this dictionary into another with preference to the existing keys. + func mergingRight(_ other: Self) -> Self { + return merging(other) { first, second in + first + } + } } public extension Optional where Wrapped == Dictionary { diff --git a/MVMCore/MVMCore/Models/JSON/JSONValue.swift b/MVMCore/MVMCore/Models/JSON/JSONValue.swift index b3527d2..0de0860 100644 --- a/MVMCore/MVMCore/Models/JSON/JSONValue.swift +++ b/MVMCore/MVMCore/Models/JSON/JSONValue.swift @@ -145,17 +145,55 @@ extension JSONValue: ExpressibleByDictionaryLiteral { } } -extension Dictionary where Key == String, Value == Any { - public func toJSONValue() throws -> [String: JSONValue] { +extension Array where Element == Any { + public func toJSONValue() throws -> JSONValue { let data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) - return try JSONDecoder().decode([String:JSONValue].self, from: data) + return try JSONDecoder().decode(JSONValue.self, from: data) + } +} + +extension Array where Element == AnyHashable { + public func toJSONValue() throws -> JSONValue { + let data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) + return try JSONDecoder().decode(JSONValue.self, from: data) + } +} + +extension Array where Element == JSONValue { + public func toJSONArray() throws -> [Any] { + let encoded = try JSONEncoder().encode(self) + guard let json = try JSONSerialization.jsonObject(with: encoded, options: .mutableContainers) as? [Any] else { + throw JSONValueError.encode + } + return json + } +} + +extension Dictionary where Key == String, Value == Any { + public func toJSONValue() throws -> JSONValue { + let data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) + return try JSONDecoder().decode(JSONValue.self, from: data) } } extension Dictionary where Key == String, Value == AnyHashable { - public func toJSONValue() throws -> [String: JSONValue] { + public func toJSONValue() throws -> JSONValue { let data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) - return try JSONDecoder().decode([String:JSONValue].self, from: data) + return try JSONDecoder().decode(JSONValue.self, from: data) + } +} + +extension Dictionary where Key == String, Value == Any { + public func toJSONValueDictionary() throws -> JSONValueDictionary { + let data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) + return try JSONDecoder().decode(JSONValueDictionary.self, from: data) + } +} + +extension Dictionary where Key == String, Value == AnyHashable { + public func toJSONValueDictionary() throws -> JSONValueDictionary { + let data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) + return try JSONDecoder().decode(JSONValueDictionary.self, from: data) } }