Jacksonを使う代わりに PostgreSQL の JSON関数を使う

JavaJSON を編集するときによく使われるのが Jackson ライブラリであろう。
巨大で階層が深い中の値を変更するのは、Jackson の JsonNode としてJSONを読み込んで、コードを書くのも
面倒くさいばかりでなく、あまり汎用的なものは期待できない。
PostgreSQL が使えるという前提にはなるが、PostgreSQLJSON関数を
テーブルを指定しないクエリの実行(SELECT 文で PostgreSQLJSON関数)をしてしまえば、
楽であろう。

DB接続するからその分パフォーマンスが落ちるものの、魅かれてしまう方法である。
(馬鹿げているかもしれないが面白い)
次の方法にする。

  • mybatis のアノテーション@Select でインターフェースだけで用意
  • mybatis のSQLMap XMLではクエリを書かない、@Select でJSON関数実行を書く
  • SQLMapper であるインターフェースに、デフォルトメソッドで @Select のメソッドを実行するようにラップする
import java.util.Arrays;
import java.util.stream.Collectors;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
 * JsonEditMapper
 */
public interface JsonEditMapper{

    /**
     * JSONテキスト内Path指定置換
     * @param json JSONテキスト文字列 
     * @param value 置換後の値、
     * @param path pathとしてキーをルートから並べた配列
     * @return 置換後のJSONテキスト文字列
     */
    default String replace(String json, Object value, String...path) {
        String val;
        if (value==null) {
            val = "null";
        }else if(value instanceof Boolean) {
            val = value.toString();
        }else if(value instanceof Number) {
            val = value.toString();
        }else{
            val = "\"" + value + "\"";
        }
        String paths = Arrays.stream(path).collect(Collectors.joining(","));
        return jsonbset(json, paths, val);
    }
    
    @Select("SELECT jsonb_set('${json}'::jsonb, '{${path}}', '${value}', false)")
    String jsonbset(@Param("json")String json, @Param("path")String path, @Param("value")String value);
}

@Select のメソッドに public スコープを付けていない。@Select のメソッドを意識せずに
デフォルトメソッドを使うようにする。
jsonb_set 関数の最後の引数 boolean を true にすれば、キーのパスが存在しなければ、
キーのパスを新しく作成してくれる。

使用例、

      String jsontxt = """
{
   "task": {
             "name": "A",
             "order" : 10,
             "limit" : "2023-12-09"
    } 
}
""";

SqlSession session が生成済であるとして、
"task"->"order" の値を変更する例

String result = session.getMapper(JsonEditMapper.class).replace(jsontxt, "Test", "task", "order");

String result = session.getMapper(JsonEditMapper.class).replace(jsontxt, true, "task", "order");

String result = session.getMapper(JsonEditMapper.class).replace(jsontxt, 1024, "task", "order");

String result = session.getMapper(JsonEditMapper.class).replace(jsontxt, null, "task", "order");