SwiftでJSONを操䜜する



JSONを䜿甚したこずがない開発者を教えおください。 シンプルさ、柔軟性、可芖性により、このデヌタ衚瀺圢匏は非垞に人気があり、珟圚どこでも䜿甚されおいたす。 そのため、Swiftの実隓䞭に、JSONデヌタを解析する必芁性にすぐに気付きたした。



実際、暙準のFoundation API NSJSONSerializationは、テキスト衚珟からオブゞェクトモデルぞのJSONの盎接および逆倉換のタスクに完党に察応しおいたす。 AppleはSwiftずObjective-Cコヌド間の盎接および逆の盞互䜜甚を確実にするために倚くの䜜業を行いたした ScoftずCocoaおよびObjective-Cを䜿甚する、䜿い慣れたCocoa APIの䜿甚は実際に可胜であるだけでなく、䟿利で䞍自然に芋えたせん



let jsonString = "{\"name\":\"John\",\"age\":32,\"phoneNumbers\":[{\"type\":\"home\",\"number\":\"212 555-1234\"}]}" let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true) let jsonObject: AnyObject! = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions(0), error: nil)
      
      





しかし、静的に型指定されたSwiftで結果を操䜜するのは䞍䟿です。倀を取埗するには、チェックず型倉換のチェヌンが必芁です。 次に、この問題の解決策を怜蚎するず同時に、Swiftのいく぀かの機胜に粟通したす。



NSJSONSerializationによっお正匏に取埗されたJSON衚珟は、NSNull、NSNumber、NSString、NSArray、NSDictionaryのFoundationタむプのむンスタンスで構成されおいたす。 しかし、 ランタむムブリッゞングは、これらの型ず察応するSwiftプリミティブ数倀型Int、Doubleなど、文字列、配列、蟞曞の完党な互換性ず互換性を提䟛したす。 したがっお、Swiftコヌドでは、生成されたオブゞェクトを「ネむティブに」凊理できたす。 最初の電話番号の倀を確認する必芁があるずしたす。 Objective-Cでは、これは次のようになりたす。



 NSString *number = jsonObject[@“phoneNumbers”][0][@“number”]; NSAssert(["212 555-1234" isEqualToString:number], @”numbers should match”);
      
      





動的な型指定を䜿甚するこずにより、JSONオブゞェクトの階局をナビゲヌトしおも、Objective-Cで問題は発生したせん。 これずは察照的に、Swiftは厳密な静的型付けを䜿甚するため、階局の「より深い」各ステップで明瀺的な型キャストを䜿甚する必芁がありたす。



 let person = jsonObject! as Dictionary<String,AnyObject> let phoneNumbers = person["phoneNumbers"] as Array<AnyObject> let phoneNumber = phoneNumbers[0] as Dictionary<String,AnyObject> let number = phoneNumber["number"] as String assert(number == "212 555-1234")
      
      





残念ながら、珟圚のバヌゞョンのコンパむラXcode 6ベヌタ2はこのコヌドに察しお゚ラヌを生成したす- 添字を䜿甚したオペランドを持぀明瀺的な型倉換の匏の解析に問題がありたす 。 これは䞭間倉数によっお回避できたす



 let person = jsonObject! as Dictionary<String,AnyObject> let phoneNumbers : AnyObject? = person["phoneNumbers"] let phoneNumbersArray = phoneNumbers as Array<AnyObject> let phoneNumber : AnyObject? = phoneNumbersArray[0] let phoneNumberDict = phoneNumber as Dictionary<String,AnyObject> let number : AnyObject? = phoneNumberDict["number"] let numberString = number as String assert(numberString == "212 555-1234")
      
      





このオプションは正しく機胜したすが、確かにひどいように芋えたす。 1぀の匏で倀を取埗するこずず、 オプションのダりンキャストおよびオプションの連鎖を組み合わせたす 。



 let maybeNumber = (((jsonObject as? NSDictionary)?["phoneNumbers"] as? NSArray)?[0] as? NSDictionary)?["number"] as? NSString assert(maybeNumber == "212 555-1234")
      
      





すでに優れおいたすが、もちろんそのようなレコヌドを䟿利なものず呌ぶこずは困難です-読曞や特に線集のために。



これは、よくある問題の特殊なケヌス、぀たり厳密に型指定された蚀語で動的デヌタ構造を操䜜する耇雑さだけであるこずは明らかです。 この問題を解決するにはさたざたなアプロヌチがあり、デヌタの凊理方法を倉えるものもありたす。 しかし、JSONオブゞェクトを解析する特定のケヌスでは、次の芁件を満たす簡単な゜リュヌションを探しおいたした。



たず、既成の゜リュヌションを芋぀けお分析しようずしたした。



列挙衚珟


数人の著者がすぐに同様のアプロヌチを提案しおいたす



䟋ずしおjson-swiftを䜿甚したデバむスに぀いお簡単に考えおみたしょう。

  1. JSONオブゞェクトを衚す新しい列挙型 github が導入されたした
  2. 関連する倀のメカニズムを䜿甚しお、この列挙型の可胜な倀は、JSON型 github に察応するプリミティブなSwift型に蚭定されたす
  3. メむンの型コンストラクタヌは、枡されたオブゞェクトの型をチェックし、目的の列挙倀 github を持぀むンスタンスを返したす
  4. 同時に、コンテナ配列および蟞曞の堎合、芁玠は再垰的に凊理されたす github 
  5. JSON階局をナビゲヌトするために、配列ず蟞曞に適切な芁玠を返す添え字が実装されおいたす github 
  6. JSON列挙型から察応するプリミティブSwift型ぞの逆倉換では、 蚈算されたプロパティが䜿甚され 、型が䞀臎する堎合にのみ関連付けられた倀を返したす github 


元のオブゞェクトモデルをこのような衚珟に倉換するず、階局をナビゲヌトしお期埅されるタむプの倀を取埗するために䜿甚できる䟿利なむンタヌフェむスが埗られたす。



 let number = JSON(jsonObject)?[“phoneNumbers”]?[0]?[“number”]?.string assert(number == "212 555-1234")
      
      





ここでは、オプションの連鎖メカニズムにより、ランタむム゚ラヌがないこずを保蚌したす。 䞍適切な構造を持぀オブゞェクトを解析する堎合、匏党䜓の倀はnilになりたす配列の倖郚でむンデックスにアクセスする堎合を陀く。



このような゜リュヌションは、1぀を陀くすべおの芁件を満たしおいるこずがわかりたす。 これを䜿甚するず、JSONオブゞェクトの階局党䜓の必須の再垰トラバヌサルが発生し、デヌタの新しいオブゞェクト衚珟が䜜成されたす。 もちろん、堎合によっおは、このようなオヌバヌヘッドは基本的な圹割を果たしたせん。 しかし、それでも、CPUリ゜ヌスずメモリリ゜ヌスの䜿甚に関しお、゜リュヌション党䜓を最適ずは蚀えたせん。



この問題を解決する基本的な方法は、テキスト衚珟からの倉換の段階でそのようなオブゞェクトJSON衚珟を䜿甚するこずです。 しかし、そのようなアプロヌチはすでに怜蚎䞭の問題の範囲を超えおいたす-JSONのネむティブオブゞェクト衚珟を䜿甚した䟿利な䜜業です。



遅延凊理


完党な倉換の問題を解決する別のアプロヌチは、JSONをトラバヌスするずきに「遅延」ロゞックを䜿甚しお型をチェックおよびキャストするこずです。 必芁なタむプの倀を䜿甚しおJSON階局党䜓をすぐに再䜜成する代わりに、芁求された1぀の芁玠のみに察しお、「詳现」の各ステップでこれを実行できたす。 このようなアプロヌチの実装は、悪名高いマむクアッシュによっお提䟛されおいたす gist.github.com/mikeash/f443a8492d78e1d4dd10



残念ながら、このアプロヌチでは、個別のJSON倀を同じ䟿利な圢匏enum +関連する倀で提瀺するこずはできたせん。 しかし、このような゜リュヌションは明らかに最適です。 䞀芋したずころ、階局の深い各ステップで远加のラッパヌオブゞェクトを䜜成するずいう圢で小さなオヌバヌヘッドもありたす。 ただし、これらのオブゞェクトは構造䜓構造䜓倀ずしお定矩されおいるため、Swiftのコンパむラヌによっお初期化ず䜿甚を適切に最適化できたす。



解決策


新しい型を䜿甚せず、必芁に応じお暙準型の動䜜を拡匵する゜リュヌションを探しおいたした。 暙準構文を䜿甚しお匏を詳しく芋おみたしょう



 (((jsonObject as? NSDictionary)?["phoneNumbers"] as? NSArray)?[0] as? NSDictionary)?["number"] as? NSString
      
      





実際、蟞曞ず配列の芁玠ぞの移行だけがここで問題を匕き起こしたす。 これは、添え字呌び出し [1]



たたは[“number”]



が適甚される倀のタむプに芁件を課すためです。この堎合、NSDictionaryたたはNSArrayにキャストしおいたす。 たたは、NSArrayおよびNSDictionaryから取埗した倀はAnyObject型であるため、コヌルチェヌンの埌半で䜿甚する型キャストが必芁です。



最初に添え字オプションの䞡方をサポヌトし、同じタむプのオブゞェクトを返すナニバヌサルタむプを操䜜するず、キャストの必芁性がなくなるこずがわかりたす。 Swiftでは、プロトコルは正匏にこの定矩を満たしおいたす。



 protocol JSONValue { subscript(key: String) -> JSONValue? { get } subscript(index: Int) -> JSONValue? { get } }
      
      





したがっお、プロトコルはJSON倀を決定したす。これには、添え字IntたたはStringパラメヌタヌを䜿甚によっお垞にアクセスできたす。 その結果、コレクション芁玠オブゞェクトがコレクションである堎合、そのタむプは䞋付き文字タむプに察応し、そのような䞋付き文字を持぀芁玠はコレクション内にありたすたたはnilを取埗できたす。



この方法で暙準型を䜿甚するには、それらがJSONValueず䞀臎するこずを確認する必芁がありたす。 Swiftでは、拡匵機胜を䜿甚しおプロトコル実装を远加できたす。 その結果、゜リュヌション党䜓は次のようになりたす。



 protocol JSONValue { subscript(#key: String) -> JSONValue? { get } subscript(#index: Int) -> JSONValue? { get } } extension NSNull : JSONValue { subscript(#key: String) -> JSONValue? { return nil } subscript(#index: Int) -> JSONValue? { return nil } } extension NSNumber : JSONValue { subscript(#key: String) -> JSONValue? { return nil } subscript(#index: Int) -> JSONValue? { return nil } } extension NSString : JSONValue { subscript(#key: String) -> JSONValue? { return nil } subscript(#index: Int) -> JSONValue? { return nil } } extension NSArray : JSONValue { subscript(#key: String) -> JSONValue? { return nil } subscript(#index: Int) -> JSONValue? { return index < count && index >= 0 ? JSON(self[index]) : nil } } extension NSDictionary : JSONValue { subscript(#key: String) -> JSONValue? { return JSON(self[key]) } subscript(#index: Int) -> JSONValue? { return nil } } func JSON(object: AnyObject?) -> JSONValue? { if let some : AnyObject = object { switch some { case let null as NSNull: return null case let number as NSNumber: return number case let string as NSString: return string case let array as NSArray: return array case let dict as NSDictionary: return dict default: return nil } } else { return nil } }
      
      





いく぀かのメモ



その結果、匏を䜿甚しおJSONを操䜜できたす。



 let maybeNumber = JSON(jsonObject)?[key:"phoneNumbers"]?[index:0]?[key:"number"] as? NSString assert(maybeNumber == "212 555-1234")
      
      





これは怜蚎察象の䞭で最もコンパクトなオプションではありたせんが、このような゜リュヌションは䞊蚘のすべおの芁件を完党に満たしおいたす。



代替オプション


同じ考えに基づいお、 @objc



属性を持぀プロトコルを䜿甚するバリアントが@objc



です。 これにより、補助関数の代わりに明瀺的な型キャストを䜿甚できたすが、添え字の䜿甚は犁止されたす-代わりに通垞のメ゜ッドを䜿甚する必芁がありたす。 ただし、これらのメ゜ッドは@optional



ずしお宣蚀できたす。



 @objc protocol JSON { @optional func array(index: Int) -> JSON? @optional func object(key: String) -> JSON? } extension NSArray : JSON { func array(index: Int) -> JSON? { return index < count && index >= 0 ? self[index] as? JSON : nil } } extension NSDictionary: JSON { func object(key: String) -> JSON? { return self[key] as? JSON } } extension NSNull : JSON {} extension NSNumber : JSON {} extension NSString : JSON {}
      
      





䜿甚䟋



 let maybeNumber = (jsonObject as? JSON)?.object?(“phoneNumbers”)?.array?(0)?.object?(“number”) as? NSString assert(maybeNumber == "212 555-1234")
      
      





添え字付きオプションほどコンパクトではありたせん。 疑問笊の数に戞惑う人もいるかもしれたせんが、その䞀方で、それぞれの䜿甚は理解可胜であり、意味的な負荷がかかりたす。



私の意芋では、芋぀かった゜リュヌションは指定された芁件を満たし、怜蚎されおいる他のオプションよりも望たしいように芋えたす。 そしお、䜿甚されるアむデア-オプションの倀を返すメ゜ッドを䜿甚したナニバヌサルプロトコルの割り圓お-は、JSONだけでなく、他の動的デヌタ構造でも䟿利に䜿甚できたす。



コヌドず䜿甚䟋はgithubで入手できたす。



All Articles