: O. Yuanying

JSON文字列とJavaオブジェクトとの相互変換

前置き

JSON文字列とJavaオブジェクトとの相互変換することができるJSON-libは、Java以外のアプリケーションとインターネット経由であれこれデータをやりとりするのになかなかシンプルで良い。

gihyo.jpの記事で出ている例のようにクライアントのJavaScriptとやりとりするサーバを開発するときなどはそのままスムーズに利用することができる。

しかし、今度はJavaでJSON形式で公開されているWebAPIを利用する際にたまに歯がゆい思いをすることがある。

{
    first_name: 'Yuan',
    last_name: 'Ying'
    age: 17
}

例えば、上のようなJSONオブジェクトを返すWebAPIをJavaのクライアントから利用したかった場合、

class Person {
    private String firstName;
    private String lastName;
    private int age;
    // and setter and getter...
}

上記のようなPersonクラスにマッピングしたくなるのが人情というものだろう。

けどそのままではうまくいかないハズ。

CamelCase

なぜなら、JSONオブジェクトのプロパティはアンダースコアで単語と単語を分けているが、PersonクラスのプロパティはCamelCase形式だから。なのでPersonクラスを以下のように書き換えればうまくいく。

class Person {
    private String first_name;
    private String last_name;
    private int age;
    // and setter and getter...
}

けどすっごくダサイ

JavaIdentifierTransformer

JSON-libではそんな場合に備えてnet.sf.json.util.JavaIdentifierTransformerというクラスが用意されている。このクラスはJSONオブジェクトをJavaクラスに変換する際にプロパティ名も変換してくれる。

デフォルトでは5つの変換方法が定義されていて、利用する際には以下のようにJSONUtilsクラスに登録する必要がある。

JSONUtils.setJavaIdentifierTransformer(JavaIdentifierTransformer.CAMEL_CASE);

JSONObject json = ...;
Person person = JSONObject.toBean( json, Person.class, classMap ); 

上記のようにアンダースコアなプロパティ名をCamelCase形式変換してくれるJavaIdentifierTransformerはデフォルトでは用意されていないので自分で用意する必要がある。

import net.sf.json.util.JavaIdentifierTransformer;

/**
 * @author yuanying
 *
 */
public class CamelCaseJavaIdentifierTransformer extends
		JavaIdentifierTransformer {

	/* (non-Javadoc)
	 * @see net.sf.json.util.JavaIdentifierTransformer#transformToJavaIdentifier(java.lang.String)
	 */
	@Override
	public String transformToJavaIdentifier(String str)       {
         if( str == null ){
            return null;
         }

         String str2 = shaveOffNonJavaIdentifierStartChars( str );

         char[] chars = str2.toCharArray();
         int pos = 0;
         StringBuffer buf = new StringBuffer();
         boolean toUpperCaseNextChar = false;
         while( pos < chars.length ){
            if( !Character.isJavaIdentifierPart( chars[pos] )
            	  || (chars[pos] == '_')
                  || Character.isWhitespace( chars[pos] ) ){
               toUpperCaseNextChar = true;
            }else{
               if( toUpperCaseNextChar ){
                  buf.append( Character.toUpperCase( chars[pos] ) );
                  toUpperCaseNextChar = false;
               }else{
                  buf.append( chars[pos] );
               }
            }
            pos++;
         }
         return buf.toString();
      }
}

バグ発見

上記のクラスを試してみると、うまくいかない。何故か知らんがぬるぽが出る。

仕方なしにJSON-libのソースを読んでみるに、JSON-lib 1.1のnet.sf.json.JSONObjectの513行目と598行目にバグがあるとしか思えない。

以下のように直す。


Class type = (Class) props.get( name );
// FIX Class type = (Class) props.get( key );

これでうまくいった。