JSONパーサにクラスとの相互変換機能を付けた

ここで作ったJSONパーサに、通常のクラスのインスタンスとの相互変換機能を追加しました。
kmaebashi.hatenablog.com
上記の時点では、JSONのテキストをJsonElementという独自クラス(インタフェース)にパースし、そのサブインタフェースのJsonElementやJsonObjectやJsonValueから値を取り出す、というところまでしか実装していませんでした。この実装でもまあ役には立つでしょうし、「任意のJSONをパースしなければいけない」という状況では特に便利でしょうが、たいていのケースではJSONで送ったり受け取ったりするデータ形式は決まっているので、それ用のクラスを作ってそこにマッピングしてくれた方が便利です。元の実装では、JsonObjectはMapを保持しているので、オブジェクトのプロパティの値を取得するためにはキーを文字列で指定するようになっていたのですが、これだとキーの名前をミスタイプしてもコンパイルエラーになってくれません。私は「動的型付け言語という間違ったアイディアでこの業界は20年遠回りした」と思っている人間なので、すべてのバグはできるだけ早く、可能ならコンパイルの段階で機械に見つけてほしいと思っています。その意味でも、静的型付け言語(Java)のクラスにマッピングするほうが仕様として「正しい」と思います。
修正版のプログラムは例によってGitHubにも上げています。
github.com

具体的な使い方としては、たとえば以下のようなクラスHogeがあったとして、

public class Hoge {
    public int intValue;
    private int privateInt; // privateフィールドは対象外
    @JsonIgnore
    public int intValueIgnore; // @JsonIgnoreを付けるとJSON化されない
    public Integer intObj; // Wrapperオブジェクトも対象。nullも使える。
    public float floatValue;
    public double doubleValue;
    public boolean booleanValue;
    public String stringValue;
    public int[] intArray;
    public Integer[] intObjArray;
    public Hoge[] hogeArray;
    public List<Hoge> hogeList;

    public void setPrivateInt(int value) {
        this.privateInt = value;
    }
}

このクラスのインスタンスJSONに変換するには、ClassMapper.toJson()メソッドを使います。

        Hoge hoge = new Hoge();
        hoge.intValue = 1;
        hoge.setPrivateInt(999);
        hoge.intValueIgnore = 9999;
        hoge.intObj = 2;
        hoge.floatValue = 3.1f;
        hoge.doubleValue = 4.1;
        hoge.booleanValue = true;
        hoge.stringValue = "abc";
        hoge.intArray = new int[] {10, 20, 30};
        hoge.intObjArray = new Integer[] {11, 21, null};
        hoge.hogeArray = new Hoge[2];
        hoge.hogeArray[0] = new Hoge();
        hoge.hogeList = new ArrayList<>();
        hoge.hogeList.add(new Hoge());
        hoge.hogeList.add(new Hoge());

        System.out.println("json.." + ClassMapper.toJson(hoge));

結果はこうなります。floatに誤差が出るのは、まあしょうがない*1

json..{
    "intValue":1,
    "intObj":2,
    "floatValue":3.0999999046325684,
    "doubleValue":4.1,
    "booleanValue":true,
    "stringValue":"abc",
    "intArray":[
        10,
        20,
        30
    ],
    "intObjArray":[
        11,
        21,
        null
    ],
    "hogeArray":[
        {
            "intValue":0,
            "intObj":null,
            "floatValue":0.0,
            "doubleValue":0.0,
            "booleanValue":false,
            "stringValue":null,
            "intArray":null,
            "intObjArray":null,
            "hogeArray":null,
            "hogeList":null
        },
        null
    ],
    "hogeList":[
        {
            "intValue":0,
            "intObj":null,
            "floatValue":0.0,
            "doubleValue":0.0,
            "booleanValue":false,
            "stringValue":null,
            "intArray":null,
            "intObjArray":null,
            "hogeArray":null,
            "hogeList":null
        },
        {
            "intValue":0,
            "intObj":null,
            "floatValue":0.0,
            "doubleValue":0.0,
            "booleanValue":false,
            "stringValue":null,
            "intArray":null,
            "intObjArray":null,
            "hogeArray":null,
            "hogeList":null
        }
    ]
}

逆にこのJSONHogeクラスに変換もできるのですが、制限として、JSONからクラスに変換するほうではListは使えません。JavaではGenericコレクションクラスを使っても実行時には要素のクラスが特定できないためです。だからJavaGenericsってのは……(以下自粛)
上のJSONから、hogeListに相当する部分を削除すれば、以下のようにClassMapper.toObject()でJSONからクラスのオブジェクトに変換できます。ClassMapper.toObject()の第2引数は、変換対象のクラスのClassです。

        String jsonStr = """
                {
                    "intValue":1,
                    "intObj":2,
                    "floatValue":3.0999999046325684,
                    "doubleValue":4.1,
                    "booleanValue":true,
                    "stringValue":"abc",
                    "intArray":[
                        10,
                        20,
                        30
                    ],
                    "intObjArray":[
                        11,
                        21,
                        null
                    ],
                    "hogeArray":[
                        {
                            "intValue":0,
                            "intObj":null,
                            "floatValue":0.0,
                            "doubleValue":0.0,
                            "booleanValue":false,
                            "stringValue":null,
                            "intArray":null,
                            "intObjArray":null,
                            "hogeArray":null,
                            "hogeList":null
                        },
                        null
                    ]
                }
                """;

        Hoge hoge = ClassMapper.toObject(jsonStr, Hoge.class);

JSONでは、「ただのintの文字列("5"とか)」も立派なJSONなので、そのあたりの変換にも対応しています。

        Integer int1 = ClassMapper.toObject("5", int.class);
        assertEquals(5, int1.intValue());

ライセンスは私の知る限りもっとも緩いライセンスであるNYSL(煮るなり焼くなり好きにしろライセンス)なので、よければ使ってやってくださいませ。

*1:Float.toString()を使うとfloatの値をきれいに文字列に変換できるようですが、このtoJson()は内部的にJsonElementを経由しているので、いったんdoubleに変換されます。