что может быть полезно.

тим, у меня есть модель, подобная следующей, которая позволяет мне строить дерево объектов Foo.

struct Foo {

    var kind : Kind

    enum Kind {
        case node([Foo])
        case leaf
    }
}

Как я могу сделать этот кодируемый, специально дляcase node([Foo])?

 Paulo Mattos06 нояб. 2017 г., 16:47
Хороший вопрос ... но я не думаю, что компилятор Swift сможет синтезироватьCodoable реализация для вас - похоже, вам нужнонаписать код самостоятельно.
 joshd06 нояб. 2017 г., 18:35
@CodeDifferent Меня действительно не волнует структура JSON, просто я могу сериализовать / десериализовать дерево. ;) Я знаю, как реализовать методы init / encode для обработки перечислений со связанными типами, но не там, где связанный тип является массивом пользовательской структуры.
 Code Different06 нояб. 2017 г., 17:24
Начните с конца: как вы хотите, чтобы JSON выглядел? Или у вас уже есть JSON и вы хотите прочитать его в Swift?
 joshd06 нояб. 2017 г., 18:37
@PauloMattos Правильно, но как реализовать декодирование и кодирование массива? Должен ли я рекурсивно перебирать его или он будет работать магически, пока Foo является Codable?

Ответы на вопрос(3)

Решение Вопроса

Базовая структура Foo:

struct Foo {

    var name: String
    var kind: Kind

    enum Kind {
        case node([Foo])
        case leaf
    }

    init(name: String, kind: Kind) {
        self.name = name
        self.kind = kind
    }
}

Расширение кодируемого протокола:

extension Foo : Codable {

    enum CodingKeys: String, CodingKey {
        case name
        case nodes
    }

    enum CodableError: Error {
        case decoding(String)
        case encoding(String)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        switch kind {
        case .node(let nodes):
            var array = container.nestedUnkeyedContainer(forKey: .nodes)
            try array.encode(contentsOf: nodes)
            break
        case .leaf:
            break
        }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // Assumes name exists for all objects
        if let name = try? container.decode(String.self, forKey: .name) {
            self.name = name
            self.kind = .leaf
            if let array = try? container.decode([Foo].self, forKey: .nodes) {
                self.kind = .node(array)
            }
            return
        }
        throw CodableError.decoding("Decoding Error")
    }
}

Расширение протокола CustomStringConvertable (для вывода строки из дерева):

extension Foo : CustomStringConvertible {

    var description: String {
        return stringDescription(self)
    }

    private func stringDescription(_ foo: Foo) -> String {
        var string = ""
        switch foo.kind {
        case .leaf:
            return foo.name
        case .node(let nodes):
            string += "\(foo.name): ("
            for i in nodes.indices {
                string += stringDescription(nodes[i])
                // Comma seperate all but the last
                if i < nodes.count - 1 { string += ", " }
            }
            string += ")"
        }
        return string
    }
}

И пример кода тестирования:

let a = Foo(name: "A", kind: .leaf)
let b = Foo(name: "B", kind: .leaf)
let c = Foo(name: "C", kind: .leaf)
let d = Foo(name: "D", kind: .node([b, c]))
let root = Foo(name: "ROOT", kind: .node([a, d]))

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try! encoder.encode(root)
let json = String(data: jsonData, encoding: .utf8)!
print("Foo to JSON:")
print(json)

let decoder = JSONDecoder()
do {
    let foo = try decoder.decode(Foo.self, from: jsonData)
    print("JSON to Foo:")
    print(foo)
} catch {
    print(error)
}

Выход:

Foo to JSON:
{
  "name" : "ROOT",
  "nodes" : [
    {
      "name" : "A"
    },
    {
      "name" : "D",
      "nodes" : [
        {
          "name" : "B"
        },
        {
          "name" : "C"
        }
      ]
    }
  ]
}
JSON to Foo:
ROOT: (A, D: (B, C))
 joshd07 нояб. 2017 г., 19:38
Хорошая командная работа !!
 joshd08 нояб. 2017 г., 01:16
Что-то еще, для полноты: более чистая реализация для этого могла бы также реализовать Codable на самом перечислении Kind. Это делает его более пригодным для повторного использования, и структура Foo может просто автоматически поддерживать Codable, поскольку базовые переменные уже соответствуют.
 Paulo Mattos07 нояб. 2017 г., 19:32
Рад, что вы опубликовали полное решение, спасибо, человек!

Вот отличный пост изDecoadable протокол и его использование.

Я думаю, что в нижней части поста вEnum В разделе вы можете найти то, что вам нужно, но если вы не хотите читать статьювот суть что может быть полезно.

Один из возможныхкодирование дляFoo Рекурсивный тип данных может быть:

struct Foo: Encodable {
    var name: String // added a per-node payload as well.
    var kind: Kind

    enum Kind {
        case node([Foo])
        case leaf
    }

    enum CodingKeys: String, CodingKey {
        case name
        case nodes
    }

    func encode(to encoder: Encoder) throws {
        var dict = encoder.container(keyedBy: CodingKeys.self)
        try dict.encode(name, forKey: .name)
        switch kind {
        case .node(let nodes):
            var array = dict.nestedUnkeyedContainer(forKey: .nodes)
            try array.encode(contentsOf: nodes)
        case .leaf:
            break // Nothing to encode. 
        }
    }
}

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

let a = Foo(name: "A", kind: .leaf)
let b = Foo(name: "C", kind: .leaf)
let c = Foo(name: "B", kind: .leaf)
let root = Foo(name: "ROOT", kind: .node([a, b, c]))

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try! encoder.encode(root)
let json = String(data: jsonData, encoding: .utf8)!
print(json)

затем выведет следующий JSON:

{
  "name" : "ROOT",
  "nodes" : [
    {
      "name" : "A"
    },
    {
      "name" : "C"
    },
    {
      "name" : "B"
    }
  ]
}

В соответствии сDecodable следует придерживаться аналогичной логики;)

 joshd07 нояб. 2017 г., 17:54
@PauloMattos: Неважно. ;) Это было на самом деле из-за ошибки в моем рекурсивном выводе строки. Выложу полный код ниже для справки в ближайшее время.
 joshd08 нояб. 2017 г., 20:34
@PauloMattos: я собираюсь пометить свой ответ проверкой, чтобы другие с большей вероятностью увидели более полный ответ. Дай мне знать, если это как-то отнимает у тебя очки за помощь.
 Hamish07 нояб. 2017 г., 02:22
Обратите внимание, что вы можете упростить весь этотswitch заявление доif case .node(let nodes) = kind { try container.encode(nodes, forKey: .nodes) } :)
 joshd07 нояб. 2017 г., 16:05
@PauloMattos: Хм. Декодирование выглядит не так просто, как ожидалось;) Я могу вытащить корневой узел, но ничего более. Может быть, проблема в кодировке или мне нужно перестроить дерево, пройдя массив узлов? Не могли бы вы опубликовать решение для init?
 joshd06 нояб. 2017 г., 23:20
Спасибо, Пауло. Я проверю это завтра, но похоже, что тыvar array = dict.nestedUnkeyedContainer(forKey: .nodes) try array.encode(contentsOf: nodes) Код - это то, чего мне не хватало.

Ваш ответ на вопрос