Created
July 31, 2019 10:14
-
-
Save zhaozzq/b0ed6948c164fccb92cf90ce46e93cfc to your computer and use it in GitHub Desktop.
Accessing Dictionaries with Key Paths
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// DictionaryExtension.swift | |
// Haiyu | |
// zhao_zzq2012@163.com | |
// Created by zhaozzq@github on 2019/7/31. | |
// Copyright © 2019 Haiyu. All rights reserved. | |
// https://oleb.net/blog/2017/01/dictionary-key-paths/ | |
// https://swift.gg/2017/01/25/dictionary-key-paths/ | |
import Foundation | |
struct KeyPath { | |
var segments: [String] | |
var isEmpty: Bool { return segments.isEmpty } | |
var path: String { | |
return segments.joined(separator: ".") | |
} | |
/// 分离首路径并且 | |
/// 返回一组值,包含分离出的首路径以及余下的键路径 | |
/// 如果键路径没有值的话返回nil。 | |
func headAndTail() -> (head: String, tail: KeyPath)? { | |
guard !isEmpty else { return nil } | |
var tail = segments | |
let head = tail.removeFirst() | |
return (head, KeyPath(segments: tail)) | |
} | |
} | |
///使用 "this.is.a.keypath" 这种格式的字符串初始化一个 KeyPath | |
extension KeyPath { | |
init(_ string: String) { | |
segments = string.components(separatedBy: ".") | |
} | |
} | |
// MARK: - 字符串字面量创建键路径 | |
extension KeyPath: ExpressibleByStringLiteral { | |
init(stringLiteral value: String) { | |
self.init(value) | |
} | |
init(unicodeScalarLiteral value: String) { | |
self.init(value) | |
} | |
init(extendedGraphemeClusterLiteral value: String) { | |
self.init(value) | |
} | |
} | |
extension Dictionary where Key == String { | |
subscript(keyPath keyPath: KeyPath) -> Any? { | |
get { | |
switch keyPath.headAndTail() { | |
case nil: | |
// 键路径为空。 | |
return nil | |
case let (head, remainingKeyPath)? where remainingKeyPath.isEmpty: | |
// 到达了路径的尾部。 | |
return self[head] | |
case let (head, remainingKeyPath)?: | |
// 键路径有一个尾部,我们需要遍历。 | |
switch self[head] { | |
case let nestedDict as [Key: Any]: | |
// 嵌套的下一层是一个字典 | |
// 用剩下的路径作为下标继续取值 | |
return nestedDict[keyPath: remainingKeyPath] | |
default: | |
// 嵌套的下一层不是字典 | |
// 键路径无效,中止。 | |
return nil | |
} | |
} | |
} | |
set { | |
switch keyPath.headAndTail() { | |
case nil: | |
// 键路径为空。 | |
return | |
case let (head, remainingKeyPath)? where remainingKeyPath.isEmpty: | |
// 直达键路径的末尾。 | |
self[head] = newValue as? Value | |
case let (head, remainingKeyPath)?: | |
let value = self[head] | |
switch value { | |
case var nestedDict as [Key: Any]: | |
// 键路径的尾部需要遍历 | |
nestedDict[keyPath: remainingKeyPath] = newValue | |
self[head] = nestedDict as? Value | |
default: | |
// 无效的键路径 | |
return | |
} | |
} | |
} | |
} | |
} | |
extension Dictionary where Key == String { | |
subscript(string keyPath: KeyPath) -> String? { | |
get { return self[keyPath: keyPath] as? String } | |
set { self[keyPath: keyPath] = newValue } | |
} | |
subscript(dict keyPath: KeyPath) -> [Key: Any]? { | |
get { return self[keyPath: keyPath] as? [Key: Any] } | |
set { self[keyPath: keyPath] = newValue } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment