Доступ к значениям AnyCodable с помощью @dynamicMemberLookup - PullRequest
0 голосов
/ 17 февраля 2019

Objc.io много говорит о легко изменяющихся нетипизированных словарях, но проблема в том, что вы не можете их легко сохранить.Я думаю, что доклад, возможно, был выпущен до того, как @dynamicMemberLookup был представлен.

AnyCodable выглядит потрясающе для простого кодирования / декодирования / сохранения простых словарей, но вы не можете легко получить доступ к членам словаря.

Мне было интересно, возможно ли / возможно ли добавить функциональность @dynamicMemberLookup, найденную в Swift 4.2 (например, в этот пример ), в AnyCodable и, если да, то как? Конечной целью было бы получить доступ / изменить вид массива или словаря без типа и сохранить их.

Итак, я попытался сделать это так:

@dynamicMemberLookup
public struct AnyCodable: Codable {
    public let value: Any

    public init<T>(_ value: T?) {
        self.value = value ?? ()
    }

    subscript(dynamicMember member: String) -> AnyCodable? {
        switch self.value {
        case let dictionary as [String: Any?]:
            return AnyCodable(dictionary[member])
        default:
            return nil
        }
    }
}

Приведенный в качестве примера словарь из AnyCodable:

let dictionary: [String: AnyEncodable] = [
    "boolean": true,
    "integer": 1,
    "double": 3.14159265358979323846,
    "string": "string",
    "array": [1, 2, 3],
    "nested": [
        "a": "alpha",
        "b": "bravo",
        "c": "charlie"
    ]
]

Если я сделаю:

if let nested = dictionary["nested"] {
    print("nested a:", nested.a)
}

, он выдаст: nested a: Optional(AnyCodable(Optional("alpha"))), что почти там!Но я хочу иметь возможность просто написать dictionary?.nested?.a ИЛИ dictionary?.array?[1], а не распаковывать nested сначала с if let nested = dictionary["nested"]. И Я хочу иметь возможность его мутировать, например: dictionary?.nested?.a? = "beta".

Хотя я не могу понять, как его преодолеть до финиша.Я, очевидно, должен был бы добавить case let array as [Any]: и т. Д. И, возможно, изменить подстрочный индекс, чтобы включить getter / setters?Но что еще мне не хватает?

Я знаю, что вы, вероятно, «не должны использовать словари таким образом» и создавать полноценную модель с пользовательским типом и все такое, но это для небольшого проекта, гдеидти по этому маршруту было бы излишним.Поэтому, пожалуйста, не отвечайте "по-другому смоделируйте свои данные".Я хочу объединить эти два существующих метода доступа / сохранения нетипизированных словарей или массивов в один.

1 Ответ

0 голосов
/ 17 февраля 2019

Хорошо, я думаю, что это в основном покрыто.

Первая проблема в том, что вы работаете со словарем.Вы можете добавить @ dynamicMemberLookup только к основному определению, поэтому вы не можете сделать это в определении словаря.Попробуйте это:

let dictionary: [String: AnyEncodable] = [ ... ]
let easierToUse = AnyCodable(dictionary)

Итак, учитывая приведенный ниже код, это то, что вам нужно?:

let dictionary: [String: AnyCodable] = [
    "boolean": true,
    "integer": 1,
    "double": 3.14159265358979323846,
    "string": "string",
    "array": [1, 2, 3],
    "nested": [
        "a": "alpha",
        "b": "bravo",
        "c": "charlie",
        "array": [
            1,
            2,
            [
                "a": "alpha",
                "b": "bravo",
                "c": "deep charlie"
            ]
        ],
    ]
]
let easierToUse: AnyCodable = AnyCodable(dictionary)

if let value = easierToUse.nested?.a {
    print(value) // prints "alpha"
}

if let value = easierToUse.nested?.array?[2]?.c {
    print(value) // prints "deep charlie"
}

if let value = easierToUse.nested?.array?[2]?.c?.value as? String {
    print(value) // prints "deep charlie"
}

Мне пришлось немного обновить ваши классы, так как вы забыли, что все они обернуты на каждом уровне:

// Helper to handle out of bounds on array with nil
extension Array {
    subscript (safe index: Int) -> Element? {
        return indices ~= index ? self[index] : nil
    }
}

@dynamicMemberLookup
public struct AnyCodable: Codable {
    public let value: Any

    public init<T>(_ value: T) {
        self.value = value
    }

    public init<T>(_ value: T?) {
        self.value = value ?? ()
    }

    subscript(dynamicMember member: String) -> AnyCodable? {
        switch self.value {
        case let anyCodable as AnyCodable:
            return anyCodable[dynamicMember: member]
        case let dictionary as [String: Any?]:
            return AnyCodable(dictionary[member] ?? nil)
        default:
            return nil
        }
    }

    subscript(index: Int) -> AnyCodable? {
        switch self.value {
        case let anyCodable as AnyCodable:
            return anyCodable[index]
        case let array as [Any]:
            return AnyCodable(array[safe: index])
        default:
            return nil
        }
    }
}
...