Swift4.0から利用可能となったCodable、便利ですよね。
Codableを用いたJSONのデコードで文字コードが原因となるエラーが発生したのでその原因と対処法を書きます。
発生したエラー
Codableを用いてData型をデコードする際にJSONDecoderでShift_JISで返ってきたJSONをdecodeしようとしたら 次のようなエラーが発生しました。
Thread 16: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Unable to convert data to string around character 1635." UserInfo={NSDebugDescription=Unable to convert data to string around character 1635.})))
どうやらData型からString型への変換に失敗している模様です。
原因
原因はJSONがShift_JISでエンコードされていたからでした。
ではなぜShift_JISはだめなのか、何ならいいのか調べてみました(UTF-8使うの当たり前じゃんという話は悲しくなるので言わないでください)
まずJSONをデコードする際に以下のようなコードを書くと思います。
JSONDecoder().decode(T.self, from: data)
このdecode
関数にdataを渡しています。
ではdecode
関数の中では何をやっているのでしょうか。
Swiftのコードを見に行くと以下のようになっています。
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
let topLevel: Any
do {
topLevel = try JSONSerialization.jsonObject(with: data)
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
}
let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
guard let value = try decoder.unbox(topLevel, as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
}
return value
}
内部的でJSONSerialization.jsonObject(with: data)
を呼び出していました。
次はJSONSerializationのjsonObject
関数を見てみます。
/* Create a Foundation object from JSON data. Set the NSJSONReadingAllowFragments option if the parser should allow top-level objects that are not an NSArray or NSDictionary. Setting the NSJSONReadingMutableContainers option will make the parser generate mutable NSArrays and NSDictionaries. Setting the NSJSONReadingMutableLeaves option will make the parser generate mutable NSString objects. If an error occurs during the parse, then the error parameter will be set and the result will be nil.
The data must be in one of the 5 supported encodings listed in the JSON specification: UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE. The data may or may not have a BOM. The most efficient encoding to use for parsing is UTF-8, so if you have a choice in encoding the data passed to this method, use UTF-8.
*/
open class func jsonObject(with data: Data, options opt: JSONSerialization.ReadingOptions = []) throws -> Any
ここに答えが書いてありました。
jsonObject
関数はUTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BEしかサポートしていないよと書いてあります。
そのため、Shift_JISでのデコードに失敗していました。
対処法
最良の方法はそもそもJSONをShift_JISでエンコードせずにUTF-8でエンコードすることでしょう。
が、やむにやまれぬ事情などでJSONはどうしてもShift_JISでエンコードされてしまう、というときには次のように単純にエンコードを無理やり変えてあげればCodableを使うことができます。
let shiftJisJsonString = String(data: self, encoding: .shiftJIS)
let utf8Json = shiftJisJsonString.data(using: .utf8)
しかしこれは本当に最後の手段にしたいところです...