eclair ではスローされて froyo ではスルーされる(JSONException (Unterminated string at character...))

改行コードを含む 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 側)がちゃんとエスケープすれば、どちらもなんの問題も起きません。

0 件のコメント:

コメントを投稿