2017-11-06 19 views
2

私はFooオブジェクトのツリーを構築するためのモデルを以下のように持っているとしましょう。再帰的列挙型を使ったSwift Codableプロトコル

struct Foo { 

    var kind : Kind 

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

具体的にはcase node([Foo])のためにこのコードを作成するにはどうすればよいですか?

Foo再帰的なデータ・タイプの一つの可能​​な エンコーディングがあり得る
+0

ニースの質問...しかし、私はスウィフトコンパイラはあなたのための 'Codoable'の実装を合成することができるようになりますとは思いません - - [コードを自分で書く]必要があるようです(https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types)。 –

+0

覚えておいてください:JSONをどのように見せたいですか?または、すでにJSONを持っていて、それをSwiftに読みたいのですか? –

+0

@ CodeDifferent私はJSON構造を気にせず、ツリーをシリアライズ/デシリアライズすることができます。 ;)関連付けられた型を持つ列挙型を処理するためにinit/encodeメソッドを実装する方法は知っていますが、関連する型はカスタム構造体の配列ではありません。 – joshd

答えて

3

はここ@PauloMattosからの回答をもとに、最終的構造体です木から):

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)) 
+0

ソリューション、ありがとう男! –

+0

良いチームワーク!! – joshd

+0

他の何か、完全性のために:これのためのよりクリーンな実装は、Kind enum自体にCodableを実装することもできます。これにより、再利用が容易になり、Foundation varsは既に準拠しているため、Foo構造体は自動的にCodableを自動的にサポートすることができます。 – joshd

3

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" 
    } 
    ] 
} 

Confor Decodableへの操作は同様のロジックに従うべきである;)

+1

Pauloに感謝します。私は明日これをテストしますが、 'var array = dict.nestedUnkeyedContainer(forKey:.nodes) try array.encode(contentsOf:nodes)'のコードのように見えます。 – joshd

+0

'switch'文全体を' if case 'に単純化することができます。ノード(ノードを許可)= kind {試みcontainer.encode(ノード、forKey:。ノード)} ':) – Hamish

+0

@PauloMattos:うーん。デコードは期待どおりに単純ではありません;)私はルートノードを引き出すことができますが、それ以上のことはできません。おそらく、エンコードに問題があるか、ノード配列を歩いてツリーを再構築する必要がありますか? initのためのソリューションを投稿するのは心配ですか? – joshd

0

Here is a great postDecoadableプロトコルとその使用法。

私はEnumセクションの記事の最後に、あなたが必要とするものを見つけることができますが、役に立つと思われる記事here is the gistを読んでいないと思います。

ベースの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プロトコル拡張(出力文字列に

関連する問題