google-api-java-clientの文字化けと対処法
Google Tasks APIにアクセスするコードを書いているのですが、App EngineのProduction環境で文字化けするパターンがあったのでまとめておきます。ちょっと変わった使い方をしていたので見つけました。
Tasks APIへのアクセスには google-api-java-client というライブラリを利用しています。このライブラリはTasks APIとJSONをやり取りし、内容をモデルクラスに変換してくれる優れ物です。OAuth 2.0のコード以外はモデルの操作で完結してしまいます。
今回はJavaScript(ブラウザ)とTasks APIの間にサーバサイド層を挟み、JavaScriptで使いやすいようにJSONを料理する仕組みを考えました。つまり、Tasks APIから受け取ったJSONをモデル化し、またJSON化してブラウザに送り出すことになります。
AccessTokenResponse token = ...; // OAuth 2.0 token GoogleAccessProtectedResource resource = new GoogleAccessProtectedResource( token.accessToken, httpTransport, jsonFactory, clientCredential.getClientId(), clientCredential.getClientSecret(), token.refreshToken); Tasks taskService = new Tasks(resource.getTransport(), resource, resource.getJsonFactory()); com.google.api.services.tasks.model.Tasks tasks = taskService.tasks.list("@default").execute(); for (Task task : tasks.getItems()) { DateTime due = task.getDue(); if (due != null) { task.put("dueTime", due.getValue()); } } String json = tasks.toString();
このようにモデルクラスの toString() メソッドで再JSON化できるのですが、落とし穴がありました。このコードはApp Engineの開発環境では正しく動作しますが、本番環境では文字化けします。
よく調べてみるとJackson JSON Processorはマルチバイトを入念に正しく扱っていましたが、Google API Clientの文字列変換部分で ByteArrayOutputStream.toString() が使われていました。このメソッドはJava VMのデフォルト文字セットに依存します。App Engineの本番環境ではデフォルト文字セットが US-ASCII になっています。
// class com.google.api.client.json.JsonFactory (google-http-client-1.5.0-beta.jar) /** * Returns a serialized JSON string representation for the given item using * {@link JsonGenerator#serialize(Object)}. * * @param item data key/value pairs * @return serialized JSON string representation */ public final String toString(Object item) { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); try { JsonGenerator generator = createJsonGenerator(byteStream, JsonEncoding.UTF8); generator.serialize(item); generator.flush(); } catch (IOException e) { throw new RuntimeException(e); } return byteStream.toString(); }
とりあえずは以下の workaround で文字化けが解消されました。
/** * Workaround for {@link JsonFactory#toString(Object)}. * * @param item data key/value pairs * @return serialized JSON string representation */ public static String toJsonString(GenericJson item) { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); try { JsonGenerator generator = item.jsonFactory.createJsonGenerator(byteStream, JsonEncoding.UTF8); generator.serialize(item); generator.flush(); } catch (IOException e) { throw new RuntimeException(e); } try { return byteStream.toString("UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } }
せっかくなのでIssue Reportを上げておきます。google-api-java-clientプロジェクトに報告すればいいのかな。偉い人教えてください。