📝

擬似API通信として、自前でJSONのコードを記述する際のdata(using:)メソッドの役割についての整理

2022/05/15に公開

表題の通りです。

実際に存在するURLを通して取得したJSONのデータをデコードする場合、ネストがあまりに深いとすぐにはパースのための構造体が組み立てられないことがあります。そういう時には、自分でも扱えそうなところまで崩して、別途JSONのコードを書いてみて叩くようにします。その際に必要となるのがdata(using:)というメソッドです。他の方のコードを見てみると強制アンラップがなされていたりと、なぜだろうかと理解できていない部分があったので整理してみました。

まとめ

JSONのデータとして受け取るには、文字コードがUTF-8のData型である必要があり、data(using:)ではオプショナルの考慮が必要だが、Swiftの文字列はデフォルトでUTF-8であるため、直接Data型にイニシャライズしてよく、その場合はオプショナルを必要としない。

.data(using: .utf8)!とは

https://developer.apple.com/documentation/foundation/nsstring/1416696-data

Returns an NSData object containing a representation of the receiver encoded using a given encoding.

指定されたエンコーディングを使用してエンコードされたレシーバーの表現を含むNSDataオブジェクトを返します。

エンコーディング

指定されたエンコーディングとは、文字コードの指定された形式への符号化を指します。これだと意味がわかりませんね。ざっくりといえば文字をどのようなバイト列に変換するかのルールであり、エクセルなどを利用していると時折見かけるようなShift_JISであったり、UTF-8、UTF-16などです。JSONの場合はBOM無しのUTF-8で記述することが定義づけられています。 これでも説明としては苦しい...

わかりやすい例だと、文字化けなどは結局この文字コードの形式が揃わないから生じています。このあたりは下記の記事が読みやすいです。

文字コードの形式による文字化けについて
https://www.morisawa.co.jp/blogs/MVP/5228

文字コードに関してより詳しく知りたい場合は「とほほのWWW入門」を
https://www.tohoho-web.com/ex/charset.html#utf-8

ドキュメントは軽く頭に入れておく程度で実際のコードを見ていきます。
擬似的なAPI通信を行うために、JSONのデータを自前で用意します。コードは仕様に則ってこのような書き方をするとしましょう。

let json = """
   {
    "CLASS_OBJ": [
        {
            "@id": "test",
            "@name": "test_name",
            "CLASS": [
                {
                    "@code": "100",
                    "@name": "平成0年(2000年)",
                    "@level": "1"
                },
	        {
                    "@code": "200",
                    "@name": "平成1年(2001年)",
                    "@level": "1"
                }
	    ]
	 }
     ]
   }
"""

けれど、これをデコードしようとすると失敗します。
なぜなら、これはただのString型の文字列でしかないからです。
実際にコードで確認してみましょう。

type(of: json) //String.Type

エンコーディングしたData型を用意する

JSONの仕様上、その文字コードはUTF-8でなければならず、さらに通信の結果として受け取る以上Data型である必要があります。そのための変換を、NSStringのインスタンスメソッドであるdata(using:)で行います。ここで登場するんですね。

引数using:で指定のできるのはString.Encodingという構造体であり、含まれるスタティックプロパティです。utf8の他にもascⅱなどがあります。
https://developer.apple.com/documentation/swift/string/encoding

let json = """
   {
        //省略
   }
""".data(using: .utf8)!

type(of: json) // Foundation.Data.Type

実際に行なって型を確認してみると、Data.typeとなっています。ここでは載せていませんがAPI通信の処理を走らせると正しく実行されるので、JSONの仕様のデータとして受け取れていることがわかります。

余談 実はString型はエンコーディングの必要がない

実は、そもそもString型は文字コードとしての指定がUTF-8であったりします。調べていてそうだったのかと驚きました。これがどういうことかといえば、今まで他の文字コードである可能性があるのでUTF-8にしなければいけないという前提で話を進めていたのですが、そもそもはじめから指定されているのだから必要ないという話です。そして、文字コードを指定する必要がなければ変換に失敗することもないため、オプショナルの返り値の扱いが要らなくなります。

このあたりは下記のQiitaの記事を読んでみてください、とても面白いです。
https://qiita.com/YOCKOW/items/530b95cfbcb22eac22f2

一応、上記のコードをdata(using:)を使わずに,JSONとして受け取れるデータ型に変えてみます。

let json = """
   {
        //省略
   }
"""
//String型はプロパティとして文字コードの情報(UInt8型)を持つ
//Data型はイニシャライズの要素の一つとしてInt型を受け取る
let data = Data(json.utf8)

type(of: data) //Foundation.Data.Type

data(using:)を利用した場合と対して変わらないようにも思われますが、String型のプロパティである.utf8は、文字列を8bitにエンコーディングした値であるため、実質的に必要であるのはData型へのイニシャライズのみとなります。

UTF-8 View
A string’s utf8 property is a collection of UTF-8 code units, the 8-bit encoding form of the string’s Unicode scalar values. Each code unit is stored as a UInt8 instance.

https://developer.apple.com/documentation/swift/string

UTF-8という文字コードに変換する必要がなくなれば、オプショナルである必要もなくなります。上記のコードでも確認できるように、強制アンラップは登場していません。

また、このように記載することもできますが、これはちょっと見にくい😅

let json = Data("""
   {
        //省略
   }
""".utf8)

書き方に関して言えば、data(using: .utf8)!は最後に追記したらいいだけであるのでわかりやすいというのはあるかもしれない。

まとめ

JSONのデータとして受け取るには、文字コードがUTF-8のData型である必要があり、data(using:)ではオプショナルの考慮が必要だが、Swiftの文字列はデフォルトでUTF-8であるため、直接Data型にイニシャライズしてよく、その場合はオプショナルを必要としない。

Discussion