Skip to content

Instantly share code, notes, and snippets.

@zhaozzq
Created July 31, 2019 10:14
Show Gist options
  • Save zhaozzq/b0ed6948c164fccb92cf90ce46e93cfc to your computer and use it in GitHub Desktop.
Save zhaozzq/b0ed6948c164fccb92cf90ce46e93cfc to your computer and use it in GitHub Desktop.
Accessing Dictionaries with Key Paths
//
// 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