Почему UnkeyedDecodingContainer требует дополнительного внешнего массива при декодировании? - PullRequest
1 голос
/ 08 апреля 2020

Я пытаюсь декодировать регулярные выражения из файла json:

{
    ...
    "expressions" : [
        {"plus": [1, 2]},
        {"less": [{"plus": [3, 4]}, 5]}
    ],
    ...
}

Я стремился к этому синтаксису, чтобы он мог вводиться максимально четко.

Изначально я использовал 2 перечислимых ключа, 1 из которых называется expressionDes c, которые могут быть числами с плавающей точкой (.f1), константами (.cnst) или операциями (.op). Связанное значение ключа операции имеет тип operatorDes c, который является вторым перечислением. Он содержит имя оператора и массив выражений для его параметров:

Использование перечисленных значений означало, что мне нужно было ввести json следующим образом:

{
    ...
    "expressions" : [
        {"op": {"plus": [{"f1":1}, {"f1":2}]}},
        {"op": {"less": [{"op": {"plus": [{"f1":3}, {"f1":4}]}}, {"f1":5}]}}
    ],
    ...
}

Что намного сложнее читать, поэтому я попытался использовать UnkeyedDecodingContainer, чтобы избежать ввода ключа типа выражения, а затем декодировать только на основе данного типа выражения:


enum operatorDesc : Decodable {
    case plus([expressionDesc])
    case less([expressionDesc])

    enum key: CodingKey { case plus; case less}
    enum CodingError: Error { case unknownFunction }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: key.self)
        do { let rawValue = try container.decode([expressionDesc].self, forKey: .plus); self = .plus(rawValue); print("Decoded \(self)") }
        catch { do { let rawValue = try container.decode([expressionDesc].self, forKey: .less); self = .less(rawValue); print("Decoded \(self)") }
                catch { throw CodingError.unknownFunction } }
    }
}

indirect enum expressionDesc : Decodable {
    case f1(f1)
    case const(String)
    case op(operatorDesc)
    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        do { let rawValue = try container.decode(Float.self); self = .f1(rawValue); print("Decoded \(self)") }
        catch { do { let rawValue = try container.decode(String.self); self = .const(rawValue); print("Decoded \(self)") }
                catch { do { let rawValue = try container.decode(operatorDesc.self); self = .op(rawValue); print("Decoded \(self)") }
                        catch {self = .f1(999); print("No expression found!") } } }
    }
}

struct sceneDesc : Decodable {
    ...
    let expressions : [expressionDesc]?
    ...
}

К сожалению, по некоторым причинам это не совсем дал расшифровку, которую я ожидал, и выдал ошибки «ожидаемый массив и получил ...», когда я попытался ввести выражения в качестве верхнего json формата. Однако я добавил дополнительные массивы вокруг выражений:

{
    ...
    "expressions" : [
        [{"plus": [[1], [2]]}],
        [{"less": [[{"plus": [[3], [4]]}], [5]]}]
    ],
    ...
}

И это действительно правильно декодирует, выдав:

Decoded f1(1.0)
Decoded f1(2.0)
Decoded plus([Mobius.expressionDesc.f1(1.0), Mobius.expressionDesc.f1(2.0)])
Decoded op(Mobius.operatorDesc.plus([Mobius.expressionDesc.f1(1.0), Mobius.expressionDesc.f1(2.0)]))
Decoded f1(3.0)
Decoded f1(4.0)
Decoded plus([Mobius.expressionDesc.f1(3.0), Mobius.expressionDesc.f1(4.0)])
Decoded op(Mobius.operatorDesc.plus([Mobius.expressionDesc.f1(3.0), Mobius.expressionDesc.f1(4.0)]))
Decoded f1(5.0)
Decoded less([Mobius.expressionDesc.op(Mobius.operatorDesc.plus([Mobius.expressionDesc.f1(3.0), Mobius.expressionDesc.f1(4.0)])), Mobius.expressionDesc.f1(5.0)])
Decoded op(Mobius.operatorDesc.less([Mobius.expressionDesc.op(Mobius.operatorDesc.plus([Mobius.expressionDesc.f1(3.0), Mobius.expressionDesc.f1(4.0)])), Mobius.expressionDesc.f1(5.0)]))

, и окончательные выражения построены так:

Mobius.expressionDesc.op(Mobius.operatorDesc.plus([Mobius.expressionDesc.f1(1.0), Mobius.expressionDesc.f1(2.0)]))

Mobius.expressionDesc.op(Mobius.operatorDesc.less([Mobius.expressionDesc.op(Mobius.operatorDesc.plus([Mobius.expressionDesc.f1(3.0), Mobius.expressionDesc.f1(4.0)])), Mobius.expressionDesc.f1(5.0)]))

Что является правильным.

Что мне интересно, есть ли способ избежать дополнительного массива [], необходимого вокруг каждого выражения в json для его декодирования? (Похоже, что они появились только при использовании UnkeyedDecodingContainer в выражении enum)

Кроме того, если кто-то знает, как лучше добиться того, что я пытаюсь сделать, я весь в ушах - вложенные do-catches станет чрезвычайно уродливым, когда я добавлю больше операций, и из того, что я прочитал, нет простого способа обойти это.

1 Ответ

1 голос
/ 08 апреля 2020

Метод decoder.unkeyedContainer предназначен для разбора массивов. Соответственно, необходимо использовать decode.singleValueContainer для отдельных значений:

indirect enum expressionDesc : Decodable {
    case f1(f1)
    case const(String)
    case op(operatorDesc)
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do { let rawValue = try container.decode(Float.self); self = .f1(rawValue); print("Decoded \(self)") }
        catch { do { let rawValue = try container.decode(String.self); self = .const(rawValue); print("Decoded \(self)") }
                catch { do { let rawValue = try container.decode(operatorDesc.self); self = .op(rawValue); print("Decoded \(self)") }
                        catch {self = .f1(999); print("No expression found!") } } }
    }
}

Я попробовал ваш код и сначала проанализировал json, и он хорошо работает:

let text = """
{
    "expressions" : [
        {"plus": [1, 2]},
        {"less": [{"plus": [3, 4]}, 5]}
    ]
}
"""

print(try! JSONDecoder().decode(sceneDesc.self, from: text.data(using: .utf8)!))

Результат:

sceneDes c (выражения: необязательные ([testapp.expressionDes c .op (testapp.operatorDes c .plus) ([testapp.expressionDes c .f1 (1.0), testapp.expressionDes c .f1 (2.0)])), testapp.expressionDes c .op (testapp.operatorDes c .less ([testapp.expressionDes c .op (testapp.operatorDes c .plus ([ testapp.expressionDes c .f1 (3.0), testapp.expressionDes c .f1 (4.0)])), testapp.expressionDes c .f1 (5.0)]))]))

...