改行コードを含む JSON を返す Servlet
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
servletResponse.setContentType("application/json");
servletResponse.setCharacterEncoding("UTF-8");
PrintWriter writer = servletResponse.getWriter();
writer.write("{\"str\" : \"HO\nGE\"}"); // !!!
writer.flush();
writer.close();
}
文字列「HOGE」のど真ん中に改行コードを含む、 JSON 的によろしくない JSON を返す Servlet を作ります。
android クライアント側
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onResume() {
super.onResume();
new Thread(new Runnable() {
@Override public void run() {
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet("http://192.168.0.5:8080/json");
HttpResponse httpResponse = null;
try {
httpResponse = httpClient.execute(httpGet);
} catch(ClientProtocolException e) {
Log.e("AndroidClient", "ClientProtocolException! " + e.getMessage());
} catch(IOException e) {
Log.e("AndroidClient", "IOException! " + e.getMessage());
}
if(httpResponse != null) {
try {
JSONObject json = new JSONObject(EntityUtils.toString(httpResponse.getEntity()));
String str = json.getString("str");
Log.d("AndroidClient", "str : " + str);
} catch(ParseException e) {
Log.e("AndroidClient", "ParseException! " + e.getMessage());
} catch(JSONException e) {
Log.e("AndroidClient", "JSONException! " + e.getMessage());
} catch(IOException e) {
Log.e("AndroidClient", "IOException! " + e.getMessage());
}
}
}
}).start();
}
んで、 Servlet にリクエスト飛ばしてレスポンスを org.json.JSONObject に変換するような android クライアントアプリを作ります。
それを eclair(2.1-update1) エミュレーター上で動かす
JSONException! Unterminated string at character 13 of {"str" : "HO
GE"}
「Unterminated string at character 13 of ...」と言われて落ちます。「android クライアント側」の 23 行目で落ちて、 29 行目のエラーログが吐かれます。
今度は froyo(2.2) エミュレーター上で動かす
str : HO
GE
あら不思議。 25 行目のログが出力されちゃうじゃありませんの!
eclair と froyo のソースを見比べる
「dalvik/libcore/json/src/main/java/org/json」以下です。
eclipse 上で「 compare with 」とかしてもあまり意味ありません。インターフェースというか public なメソッドのシグニチャーだけ同じで実装は随分違っている雰囲気です。
JSONObject に食わせた String をパースする org.json.JSONTokener を見比べてみると・・・
public class JSONTokener {
// (中略)
public String nextString(char quote) throws JSONException {
char c;
StringBuilder sb = new StringBuilder();
for (;;) {
c = next();
switch (c) {
case 0:
case '\n':
case '\r':
throw syntaxError("Unterminated string");
上は eclair
下が froyo (private な深いメソッドまで行ってますが、まあこの辺だと思います)
public class JSONTokener {
// (中略)
private int nextCleanInternal() throws JSONException {
while (pos < in.length()) {
int c = in.charAt(pos++);
switch (c) {
case '\t':
case ' ':
case '\n':
case '\r':
continue;
スルーとスローは随分違うと思いますが、 JSON を返す側(ここでは Servlet 側)がちゃんとエスケープすれば、どちらもなんの問題も起きません。