post Image
Swift – introducing swift-json, even swiftier than SwiftyJSON

swift-jsonをゼロから書き直したのでおしらせします。

JSON as a Swift Literal

どういうものかというと、こういうものです。

let json:JSON = [
    "null":     nil,
    "bool":     true,
    "int":      -42,
    "double":   42.195,
    "string":   "漢字、カタカナ、ひらがなと\"引用符\"の入ったstring😇",
    "array":    [nil, true, 1, "one", [1], ["one":1]],
    "object":   [
        "null":nil, "bool":false, "number":0, "string":"" ,"array":[], "object":[:]
    ],
    "url":"https://github.com/dankogai/"
]

はい。いきなりJSON型としてリテラルで書き下せます。

JSON型はCustomStringConvertibleで、その.descriptionはECMASCriptのJSON.stringify()で出力される文字列そのものです。

{
    "null":     null,
    "bool":     true,
    "int":      -42,
    "double":   42.195,
    "string":   "漢字、カタカナ、ひらがなと\"引用符\"の入ったstring😇",
    "array":    [null, true, 1, "one", [1], {"one":1}],
    "object":   {
        "null":null, "bool":false, "number":0, "string":"" ,"array":[], "object":{}
    },
    "url":"https://github.com/dankogai/"

}

初期化はリテラルからはもちろん、文字列からでもできますし…

JSON(string:"{\"swift\":[\"safe\",\"fast\",\"expressive\"]}")

URLの中身からでもできます。

JSON(urlString:"https://api.github.com")

Direct Manipulation

letではなくvarの場合、中身を直接かつ直感的に操作することも可能です。

var json = JSON([])
json[0] = nil
json[1] = true
json[2] = 1

一つ気をつけなければならないのは、右側にあるリテラルはビルトインのniltrue1ではなく、JSON型の.Null.Bool(true).Number(1)であること。よって以下はエラーになります。

let one = "one"
json[3] = one // error: cannot assign value of type 'String' to type 'JSON'

ただしこの場合は、次のようにunwrapしてしまえばいいのです。

json[1].bool   = Bool(true)
json[2].number = Int(1)
json[3].string = String("one")
json[4].array  = [Int(1)]
json[5].object = [String("one"):Int(1)]

これらは getter であるとともに setter でもあり、 getter としては、型が合えばJSON型から元の型の値を返し、合わなければnilを返すようになっています。

json[1].bool    // Optional(true)
json[1].number  // nil

その特性を活かすと、こんな書き方もできます。

json[2].number! += 1            // now 2
json[3].string!.removeLast()    // now "on"
json[4].array!.append(2)        // now [1, 2]
json[5].object!["two"] = 2      // now ["one":1,"two":2]

JSON.Arrayの場合に、存在しない添字に値を代入すると、配列が拡張され隙間は.Nullで埋まります。

json[10] = false    // json[6...9] are null

.Objectの場合に存在しないキーに対して値を代入した場合も同様です。つまり、JSON型はECMAScriptのobjectのごとく振る舞います。

Protocol Conformance

JSON型は次のプロトコルに準拠しています。

  • Equatableなので直接比較ができます
JSON(["two":2]) == JSON(["t"+"w"+"o":1+1])
  • HashableなのでDictionaryのキーとしても使えます。

  • ExpressibleBy*Literalなので、リテラルを直接書き下せます。前述の通りです。

  • CustomStringConvertible で、文字列化すると正しいJSON表記になります。

  • CodableでもあるのでJSONENcoder()の代わりになったりもします。

  • Sequenceでもあります。が、Swiftの制限により直感性が少し落ちています。

for v in JSON([nil, true, 1, "one", [1], ["one":1]]) {
   // v に入るのは?
}

上記の例だとvJSONではなく(IteratorKey,JSON)で、IteratorKeyは配列の場合は.Index、辞書の場合は.Keyとなります。それぞれアクセサーが用意されているので

for (i, v) in JSON([nil, true, 1, "one", [1], ["one":1]]) {
  // 添字は i ではなく i.index
}
for (k, v) in JSON([
        "null":nil, "bool":false, "number":0, "string":"" ,
        "array":[], "object":[:]
    ]) {
  // 添字は k ではなく k.key
}

と出来はします。が、これは先ほどの.array.objectの方が使いやすいでしょう。

for v in JSON([nil, true, 1, "one", [1], ["one":1]]).array! {
    // ...
}
for (k, v) in JSON([
        "null":nil, "bool":false, "number":0, "string":"" ,
        "array":[], "object":[:]
    ]).object! {
    // ...
}

なお当然のことですが、nullboolnumberstringは空のSequenceになります。

Error handling

init?init() throwsはJSONとは相性が悪いのでJSON型では採用せず、その代わりエラーが起こったら.Error(.ErrorType)型の値を返し、それが連鎖するようになっています。エラーになったのか、そしてなった場合はどんなエラーだったのかを確認するには次のようにすればOKです。

if let e = json.error {
    debugPrint(e.type)
    if let nsError = e.nsError {
        // do anything with nsError
    }
}

Usage

build

$ git clone https://github.com/dankogai/swift-json.git
$ cd swift-json # the following assumes your $PWD is here
$ swift build

REPL

次のようにしてREPLで実行できます。

$ scripts/run-repl.sh

REPLが上がったらimport JSONしてお楽しみ下しあ。

  1> import JSON
  2> let json:JSON = ["swift":["safe","fast","expressive"]]
json: JSON.JSON = Object {
  Object = 1 key/value pair {
    [0] = {
      key = "swift"
      value = Array {
        Array = 3 values {
          [0] = String {
            String = "safe"
          }
          [1] = String {
            String = "fast"
          }
          [2] = String {
            String = "expressive"
          }
        }
      }
    }
  }
}

Xcode

.xcodeprojはレポジトリには含まれていません。Swift Package Managerでswift package generate-xcodeprojすれば生成されるので。一応

$ scripts/prep-xcode

でそれをやった上でworkspaceを開いてくれます。

iOS and Swift Playground

現状 Swift Package Manager と macOS/Linux 以外のプラットフォームとの相性は悪く、特に Playgrounds はモジュールすらサポートしてません。しかし依存コードをSourcesフォルダーに置けば援用してくれます。モジュール本体は[JSON.swift]だけなので、それを手でコピーしても良いですし、

$ scripts/ios-prep.sh

を実行するとレポジトリのiOS/JSON.playgroundをよきに計らってくれるのであとはそれを iCloud Drive ぶっこんで同期すれば、Playgrounds for iPad でもお楽しみになれます。

IMG_0074.png

From Your SwiftPM-Managed Projects

とはいえ基本は Swift Package Manager の利用が前提です。

dependenciesに以下を、

.package(
  url: "https://github.com/dankogai/swift-json.git", from: "4.0.0"
)

.target に以下を追加したら、あとは

.target(
  name: "YourSwiftyPackage",
  dependencies: ["JSON"])
import JSON

してお楽しみください。

犯行動機

むしゃくしゃして書き直した。後悔はしていない…というのは冗談にしても、当初 swift-json は class でした。

https://qiita.com/dankogai/items/e3d9882f4d25bc44cc03

これはJSON Schemaとしての利用を念頭においてそうしたのですが、Swift 4 でCodableが入ったことにより陳腐化しました。

それではJSONDecoderJSONEncoderさえあればいいかというとそうは行きません。フリーフォームのJSONにアクセスする需要は常にあるからです。そういうことならSwiftyJSONを使えばいいじゃないかという疑問は当然ですが、以下の理由によりSwiftyJSONでは私の需要を満たせなかったのです。

  • SwiftyJSON は framework をビルドして使うことを前提としていること。それでは Playgrounds for iOS で動かせないではないか。これが一番の理由
  • SwiftyJSON はJSONSerializationの吐いたJSONObjectつまりAnyをそのまま援用していること。要素にアクセスする都度Anyを unwrap するというのは Swifty とは言えません。対して本モジュールのJSONenumで、Anyは一切入ってません。JSONSerialization叩くのはStringからJSONへ変換するときだけで、JSONからStringへの変換はJSONSerializationに全く依存しません。別のParserがあればすぐに差し替えられます。少なくともこの一点に関しては異論なく swiftier なはず。

  • SwiftyJSONのJSON.swiftは1500行以上あるのに対し、本モジュールのそれは現時点で350行切ってます。これくらいなら前述の Playgrounds for iPad の事例のようにファイルを一つコピーすれば事足ります。

私からは以上です。 Enjoy!

Dan the Safe, Fast, and Expressive


『 Swift 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。