TSVの書き込み

読込みと同じ方法で対応できる。
oboe2uran.hatenablog.com

TSVの書き込みの場合の継承元は、yipuran-csv の Wiki - CSV書き込みを参照

  • CsvCreator と同じ方法の interface TsvCreator
  • CsvwriteArrayStream と同じ方法の interface TsvwriteArrayStream
  • CsvwriteListStreamと同じ方法の interface TsvwriteListStream

いずれも、org.yipuran.csv4j.CSVWriter を使う時に、デリミタ措定を
カンマ区切りから、タブ区切りに変更することと、
1行分作成の Stream の join でタブ文字を指定するように
変更することである。

使い方は、yipuran-csv の Wiki - CSV書き込み と同じである。

TsvCreator

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.yipuran.csv.BOMfunction;
import org.yipuran.csv4j.CSVWriter;
/**
 * TSV作成 interface
 */
@FunctionalInterface
public interface TsvCreator extends Serializable{
    /**
     * TSV出力行数分の Colleactionを返す Supplier を取得する.
     * @return Supplier<Collection<String[]>>
     */
    public Supplier<Collection<String[]>> getSupplier();

    /**
     * TSV出力実行.
     * @param out OutputStream
     * @param charName 文字セット名
     */
    default public void create(OutputStream out, String charName){
        try(OutputStreamWriter writer = new OutputStreamWriter(out, charName)){
            CSVWriter csvWriter = new CSVWriter(writer, '\t');
            for(String[] s:getSupplier().get()){
                for(int i=0;i < s.length;i++){
                    s[i] = s[i]==null ? "" : s[i];
                }
                csvWriter.writeLine(s);
            }
        }catch(Exception e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    /**
     * ダブルクォート括りTSV出力実行.
     * @param out OutputStream
     * @param charName 文字セット名
     */
    default public void createWithDblQuot(OutputStream out, String charName){
        String lineSeparator = System.getProperty("line.separator");
        try(OutputStreamWriter writer = new OutputStreamWriter(out, charName)){
            for(String[] sary:getSupplier().get()){
                for(int i=0;i < sary.length;i++){
                    sary[i] = sary[i]==null ? "" : sary[i];
                }
                writer.write(tsvline(sary));
                writer.write(lineSeparator);
            }
        }catch(Exception e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    /**
     * BOM付きUTF-8 TSV出力実行
     * @param out OutputStream
     */
    default public void createBomUTF8(OutputStream out){
        try(OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)){
            BOMfunction.push(out);
            CSVWriter csvWriter = new CSVWriter(writer, '\t');
            for(String[] sary:getSupplier().get()){
                for(int i=0;i < sary.length;i++){
                    sary[i] = sary[i]==null ? "" : sary[i];
                }
                csvWriter.writeLine(sary);
            }
        }catch(Exception e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    /**
     * BOM付きUTF-8ダブルクォート括り TSV出力実行
     * @param out OutputStream
     */
    default public void createBomUTF8WithDblQuot(OutputStream out){
        String lineSeparator = System.getProperty("line.separator");
        try(OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)){
            BOMfunction.push(out);
            for(String[] sary:getSupplier().get()){
                for(int i=0;i < sary.length;i++){
                    sary[i] = sary[i]==null ? "" : sary[i];
                }
                writer.write(tsvline(sary));
                writer.write(lineSeparator);
            }
        }catch(Exception e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    /**
     * ダブルクォート括り1行作成.
     * @param ary 1行生成する配列
     * @return ダブルクォート括り1行
     */
    default public String tsvline(String[] ary){
        return "\"" + Arrays.stream(ary).map(s->s.replaceAll("\"","\"\""))
                   .collect(Collectors.joining("\"\t\"")) + "\"";
    }
}

TsvwriteArrayStream
String[] の Stream を指定する場合の為に

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.yipuran.csv.BOMfunction;
import org.yipuran.csv4j.CSVWriter;
/**
 * 配列 Stream から生成
 */
@FunctionalInterface
public interface TsvwriteArrayStream{
    /**
     * TSV出力行数分の String配列の Stream を返す Supplier を取得する.
     * @return Supplier<Stream<String[]>>
     */
    Supplier<Stream<String[]>> getSupplier();

    /**
     * TSV出力実行.
     * @param out OutputStream
     * @param charName 文字セット名
     */
    default public void create(OutputStream out, String charName){
        try(OutputStreamWriter writer = new OutputStreamWriter(out, charName)){
            CSVWriter csvWriter = new CSVWriter(writer, '\t');
            getSupplier().get().map(s->{
                for(int i=0;i < s.length;i++){
                    s[i] = s[i]==null ? "" : s[i];
                }
                return s;
            }).forEach(t->{
                try{
                    csvWriter.writeLine(t);
                }catch(IOException ex){
                    throw new RuntimeException(ex);
                }
            });
        }catch(Exception ex){
            throw new RuntimeException(ex.getMessage(), ex);
        }
    }
    /**
     * ダブルクォート括りTSV出力実行.
     * @param out OutputStream
     * @param charName 文字セット名
     */
    default public void createWithDblQuot(OutputStream out, String charName){
        String lineSeparator = System.getProperty("line.separator");
        try(OutputStreamWriter writer = new OutputStreamWriter(out, charName)){
            getSupplier().get().map(s->{
                for(int i=0;i < s.length;i++){
                    s[i] = s[i]==null ? "" : s[i];
                }
                return s;
            }).forEach(t->{
                try{
                    writer.write(tsvline(t));
                    writer.write(lineSeparator);
                }catch(IOException ex){
                    throw new RuntimeException(ex);
                }
            });
        }catch(Exception e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    /**
     * BOM付きUTF-8 TSV出力実行
     * @param out OutputStream
     */
    default public void createBomUTF8(OutputStream out){
        try(OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)){
            BOMfunction.push(out);
            CSVWriter csvWriter = new CSVWriter(writer, '\t');
            getSupplier().get().map(s->{
                for(int i=0;i < s.length;i++){
                    s[i] = s[i]==null ? "" : s[i];
                }
                return s;
            }).forEach(t->{
                try{
                    csvWriter.writeLine(t);
                }catch(IOException ex){
                    throw new RuntimeException(ex);
                }
            });
        }catch(Exception e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    /**
     * BOM付きUTF-8ダブルクォート括り TSV出力実行
     * @param out OutputStream
     */
    default public void createBomUTF8WithDblQuot(OutputStream out){
        String lineSeparator = System.getProperty("line.separator");
        try(OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)){
            BOMfunction.push(out);
            getSupplier().get().map(s->{
                for(int i=0;i < s.length;i++){
                    s[i] = s[i]==null ? "" : s[i];
                }
                return s;
            }).forEach(t->{
                try{
                    writer.write(tsvline(t));
                    writer.write(lineSeparator);
                }catch(IOException ex){
                    throw new RuntimeException(ex);
                }
            });
        }catch(Exception e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    /**
     * ダブルクォート括り1行作成.
     * @param ary 1行生成する配列
     * @return ダブルクォート括り1行
     */
    default public String tsvline(String[] ary){
        return "\"" + Arrays.stream(ary).map(s->s.replaceAll("\"","\"\""))
                   .collect(Collectors.joining("\"\t\"")) + "\"";
    }
}

TsvwriteListStream
List<String> の Stream から生成

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.yipuran.csv.BOMfunction;
import org.yipuran.csv4j.CSVWriter;

/**
 * List<String> の Stream から生成
 */
@FunctionalInterface
public interface TsvwriteListStream{

    Supplier<Stream<List<String>>> getSupplier();

    /**
     * TSV出力実行.
     * @param out OutputStream
     * @param charName 文字セット名
     */
    default public void create(OutputStream out, String charName){
        try(OutputStreamWriter writer = new OutputStreamWriter(out, charName)){
            CSVWriter csvWriter = new CSVWriter(writer, '\t');
            getSupplier().get().map(list->list.stream().map(s->s==null ? "" : s)
            .collect(Collectors.toList()))
            .forEach(t->{
                try{
                    csvWriter.writeLine(t);
                }catch(IOException ex){
                    throw new RuntimeException(ex);
                }
            });
        }catch(Exception ex){
            throw new RuntimeException(ex.getMessage(), ex);
        }
    }
    /**
     * ダブルクォート括りTSV出力実行.
     * @param out OutputStream
     * @param charName 文字セット名
     */
    default public void createWithDblQuot(OutputStream out, String charName){
        String lineSeparator = System.getProperty("line.separator");
        try(OutputStreamWriter writer = new OutputStreamWriter(out, charName)){
            getSupplier().get().map(list->list.stream().map(s->s==null ? "" : s)
            .collect(Collectors.toList()))
            .forEach(t->{
                try{
                    writer.write(tsvline(t));
                    writer.write(lineSeparator);
                }catch(IOException ex){
                    throw new RuntimeException(ex);
                }
            });
        }catch(Exception e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    /**
     * BOM付きUTF-8 TSV出力実行
     * @param out OutputStream
     */
    default public void createBomUTF8(OutputStream out){
        try(OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)){
            BOMfunction.push(out);
            CSVWriter csvWriter = new CSVWriter(writer, '\t');
            getSupplier().get().map(list->list.stream().map(s->s==null ? "" : s)
            .collect(Collectors.toList()))
            .forEach(t->{
                try{
                    csvWriter.writeLine(t);
                }catch(IOException ex){
                    throw new RuntimeException(ex);
                }
            });
        }catch(Exception e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    /**
     * BOM付きUTF-8ダブルクォート括り TSV出力実行
     * @param out OutputStream
     */
    default public void createBomUTF8WithDblQuot(OutputStream out){
        String lineSeparator = System.getProperty("line.separator");
        try(OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)){
            BOMfunction.push(out);
            getSupplier().get().map(list->list.stream().map(s->s==null ? "" : s)
            .collect(Collectors.toList()))
            .forEach(t->{
                try{
                    writer.write(tsvline(t));
                    writer.write(lineSeparator);
                }catch(IOException ex){
                    throw new RuntimeException(ex);
                }
            });
        }catch(Exception e){
            throw new RuntimeException(e.getMessage(), e);
        }
    }
    /**
     * Collection→ダブルクォート括り1行作成.
     * @param list 1行生成する文字列 List
     * @return ダブルクォート括り1行
     */
    default public String tsvline(List<String> list){
        return "\"" + list.stream().map(s->s.replaceAll("\"","\"\""))
                   .collect(Collectors.joining("\"\t\"")) + "\"";
    }
}

TSVの読込み

昔作成して公開している CSVの読み書き、
 GitHub - yipuran/yipuran-csv: Java CSV read and write
これは、TSV ファイルで使用することを目的にしていなかった。
長いこと開発をしていて、なかなかTSVファイルを対象にすることが少なかった。
この yipuran-csv の中の、Csvprocess と同じ機能で、TSV読込みは、継承をすることで
次のように用意できる。
 Csvprocess の説明は、→ こちらのWiki
継承して、TSV用に用意するポイントは、

  • 内部で使用する org.yipuran.csv4j.CSVReader のインスタンス生成で、デリミタをカンマ区切りからタブ区切りにすること

後は、Csvprocess と同じメソッドの仕様で使える。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.yipuran.csv.BOMfunction;
import org.yipuran.csv.Csvprocess;
import org.yipuran.csv4j.CSVReader;
import org.yipuran.csv4j.ParseException;
import org.yipuran.csv4j.ProcessingException;
/**
 * Tsvprocess : Csvprocessを継承したTSV読込クラス
 */
public class Tsvprocess extends Csvprocess{
    private boolean blankIsNull = false;
    private static final char DELIMITER = '\t';
    public Tsvprocess(){}
    
    public Tsvprocess(boolean blankIsNull) {
        this.blankIsNull = blankIsNull;
    }
    /**
     * ヘッダ有りTSV読込み実行.
     * @param inReader InputStreamReader
     * @param header ヘッダ行 Consumer
     * @param processor コンテンツ行BiConsumer、TSV行読込みカウント(1始まり)とTSV文字列のList
     * @throws IOException
     * @throws ProcessingException
     */
    @Override
    public void read(InputStreamReader inReader, Consumer<List<String>> header, BiConsumer<Integer, List<String>> processor) 
    throws IOException, ProcessingException{
        CSVReader reader = new CSVReader(new BufferedReader(inReader),
                                         Charset.forName(inReader.getEncoding()), 
                                         DELIMITER,
                                         getComment(),
                                         blankIsNull);
        try{
            int lineCount = 0;
            while(true){
                try{
                    List<String> fields = reader.readLine();
                    if (fields.size()==0) break;
                    if (isHasHeader() && lineCount==0){
                        String rep = fields.get(0);
                        if (BOMfunction.match(rep)) {
                            fields.remove(0);
                            fields.add(0, BOMfunction.chop(rep));
                        }
                        header.accept(fields);
                    }else{
                        processor.accept(lineCount, fields);
                    }
                }catch(Exception e){
                    throw new ProcessingException(e, reader.getLineNumber());
                }
                lineCount++;
            }
        }finally{
            reader.close();
        }
    }
    /**
     * ヘッダ無しTSV読込み実行.
     * @param inReader InputStreamReader
     * @param processor BiConsumer 行のindexとTSV文字列のList
     * @throws IOException
     * @throws ProcessingException
     * @throws ParseException
     */
    @Override
    public void readNoheader(InputStreamReader inReader, BiConsumer<Integer, List<String>> processor)
    throws IOException, ProcessingException, ParseException{
        CSVReader reader = new CSVReader(new BufferedReader(inReader),
                                         Charset.forName(inReader.getEncoding()),
                                         DELIMITER,
                                         getComment(),
                                         blankIsNull);
        try{
            int lineIndex = 0;
            while(true){
                try{
                    List<String> fields = reader.readLine();
                    if (fields.size()==0) break;
                    if (lineIndex==0){
                        String rep = fields.get(0);
                        if (BOMfunction.match(rep)) {
                            fields.remove(0);
                            fields.add(0, BOMfunction.chop(rep));
                        }
                    }
                    processor.accept(lineIndex, fields);
                }catch(Exception e){
                    throw new ProcessingException(e, reader.getLineNumber());
                }
                lineIndex++;
            }
        }finally{
            reader.close();
        }
    }

    /**
     * ヘッダ有りTSV読込み実行(Map形式読込み).
     * <PRE>
     * ヘッダ行列をキーとして読込み結果をMapで実行
     * </PRE>
     * @param inReader InputStreamReader
     * @param processor コンテンツ行BiConsumer、TSV行読込みカウント(1始まり)とヘッダのキーに対するコンテンツ行の値のMap
     * @throws IOException
     * @throws ProcessingException
     */
    @Override
    public void read(InputStreamReader inReader, BiConsumer<Integer, Map<String, String>> processor)
    throws IOException, ProcessingException{
        CSVReader reader = new CSVReader(new BufferedReader(inReader),
                                         Charset.forName(inReader.getEncoding()),
                                         DELIMITER,
                                         getComment(),
                                         blankIsNull);
        try{
            Map<Integer, String> headerMap = new HashMap<>();
            int lineCount = 0;
            while(true){
                try{
                    List<String> fields = reader.readLine();
                    if (fields.size()==0) break;
                    if (isHasHeader() && lineCount==0){
                        String rep = fields.get(0);
                        if (BOMfunction.match(rep)) {
                            fields.remove(0);
                            fields.add(0, BOMfunction.chop(rep));
                        }
                        int i = 0;
                        for(String key:fields){
                            headerMap.put(i, key);
                            i++;
                        }
                    }else{
                        processor.accept(lineCount,
                            Stream.iterate(0, i->i+1).limit(fields.size())
                            .collect(HashMap<String, String>::new, 
                                     (r, t)->r.put(headerMap.get(t), fields.get(t)), (r, t)->{})
                        );
                    }
                }catch(Exception e){
                    throw new ProcessingException(e, reader.getLineNumber());
                }
                lineCount++;
            }
        }finally{
            reader.close();
        }
    }
}

この Tsvprocess を使用するサンプルは、、、
文字コードUTF-8 で1行目はヘッダ行で読込み
↓ サンプル無いの in変数は、InputStream

Tsvprocess process = new Tsvprocess();
process.read(in, StandardCharsets.UTF_8
, h->{
    // h=List<String> ヘッダ行
}, (i, p)->{
    // i=TSV行番号:1始まり、 p=1行の List<String>
});

文字コードUTF-8 でヘッダ無しで読込み
↓ サンプル無いの in変数は、InputStream

Tsvprocess process = new Tsvprocess();
process.readNoheader(in, StandardCharsets.UTF_8, (n, p)->{
    // n=行番号(0始まり、インデックスとしてのカウント) p=1行の List<String>
});

文字コードUTF-8 で1行目はヘッダ行で、マップのキーとして読み込む

Tsvprocess process = new Tsvprocess(true);
process.read(in, StandardCharsets.UTF_8, (n, m)->{
    // n=行番号(1始まり、ヘッダを除くため) m=1行の Map<String, String>
    m.entrySet().stream().forEach(e->{
        // e.getKey()=ヘッダキー e.getValue()=値
    });
});

JsonNode を複製した操作に気をつける。

うっかり shallow copy のまま操作してしまうのを注意したい。
サンプル

{
  "A" : "a",
  "B" : {
    "B01" : "b01-01234",
    "B02" : {
      "detail" : "test_sample",
      "name" : {
        "type" : "merge",
        "len" : 12
      }
    }
  },
  "C" : [ 100, 160, 300 ]
}

この JSONを表すString jsonstr から生成する JsonNode を以下のようにすると
shallow copy になってしまう。

ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(jsonstr);
JsonNode node2 = node;

((ObjectNode)(node2.path("B").path("B02"))).put("detail", "test_sample");
((ObjectNode)(node2.path("B").path("B02"))).replace("name", mapper.createObjectNode()
                                           .put("type", "merge").put("len", 12));
((ArrayNode)(node2.path("C"))).set(1, 160);

node2 を変更しているつもりが、node を変更していることになる。
ディープコピー(deepCopy)を使うようにして元の JsonNode が変更されないようにする

JsonNode node2 = node.deepCopy();

((ObjectNode)(node2.path("B").path("B02"))).put("detail", "test_sample");
((ObjectNode)(node2.path("B").path("B02"))).replace("name", mapper.createObjectNode()
                                           .put("type", "merge").put("len", 12));
((ArrayNode)(node2.path("C"))).set(1, 160);

node2 は、

{
  "A" : "a",
  "B" : {
    "B01" : "b01-01234",
    "B02" : {
      "detail" : "test_sample",
      "name" : {
        "type" : "merge",
        "len" : 12
      }
    }
  },
  "C" : [ 100, 160, 300 ]
}

MySQL のJSON型の path書式

PostgreSQL とは違って、ルートを$文字で指定して、"->" による連結ではなく
JSON列名 -> path
 あるいは、
JSON列名 ->> path
で、文字列とての参照になる。
path 部分が、$から始まってJSONキーを '.' ピリオド区切り文字で指定する。

例) jdataという列データが以下の時、

{
  "limit": "2024-03-01",
  "other": {
    "len": 38,
    "label": "Abc",
    "width": 23,
    "options": [
      "A",
      "B",
      "C"
    ]
  },
  "price": 320
}

other の label を抽出

SELECT jdata->>'$.other.label' 

priceを文字列として抽出

SELECT jdata->>'$.price'     

priceを抽出 → DECIMAL に CASTする

SELECT cast(jdata->'$.price' as DECIMAL) 

limit の日付を DATE 型へ

SELECT cast(jdata->'$.limit' as DATE)  

配列の先頭

SELECT jdata->>'$.other.options[0]'    

MySQL の JSON_SET関数

PostgreSQL JSON_SET/JSONB_SET と同じ要領で使うと失敗する。
  JSON_SET(json_doc, path, val[, path, val] ...)

  • path の指定は、JSONルートの $ から、区切り文字 '.' ピリオドで指定する。
  • PostgreSQL のようにキーが存在しなかったらセットする/しないの
    true/false 指定はなく存在しない場合は挿入、存在すれば更新である。

次のようにPostgreSQL と同様に path を指定してしまうと、

SET @j = '{"title": "Alpha", "other": {"label": "Abc", "width": 23} }';
SELECT json_set(@j, 'other,width', 80)

Error Code: 3143. Invalid JSON path expression
となる。
正しくは、、

SET @j = '{"title": "Alpha", "other": {"label": "Abc", "width": 23} }';
SELECT json_set(@j, '$.other.width', 80)

配列の更新は、[ index ] で、

SET @j = '{"title": "Alpha", "other": {"label": "Abc", "width": 90, "options":[ "X", "B", "C"  ]} }';
SELECT json_set(@j, '$.other.options[1]', 'a')
{"other": {"label": "Abc", "width": 90, "options": ["X", "a", "C"]}, "title": "Alpha"}

null をセットする時、PostgreSQL のケースとは異なって、、
json_set/jsonb_set で NULL をセットする時の注意 - Oboe吹きプログラマの黙示録
直接、null を指定して実行する。

SET @j = '{"title": "Alpha", "other": {"label": "Abc", "width": 23} }';
SELECT json_set(@j, '$.other.width', null)
{"other": {"label": "Abc", "width": null}, "title": "Alpha"}

JSON の一部の key value を削除する ( Jackson JsonNode )

巨大なJSON、階層が深かったり値の文字列がとても長かったりする時は、整形したとしても
とても見にくく目視確認に困る。
確認しなくても済むものは、取り除いてしまいたい。
以下、そんなに大きくないJSON でそういう場合の方法、Jacksonライブラリの JsonNode
対象JSON が読み込めている前提でサンプルを示す。

{
  "A" : 123,
  "B" : {
    "B01" : "b01-01234",
    "B02" : {
      "detail" : "詳細~",
      "name" : "orange"
    }
  },
  "C" : [ {
    "C01" : {
      "size" : 230
    }
  }, {
    "C02" : {
      "size" : 380
    }
  }, {
    "C03" : {
      "size" : 670
    }
  } ],
  "D" : "Document"
}

このJSON文字列が読み込んだ結果の JsonNode インスタンスnode だとする。

"D" : "Document" を取り除く場合
ObjectNode に cast して、JsonNode remove(String propertyName) メソッドを実行する。

((ObjectNode)node).remove("D");

node インスタンスから "D" key と valueが削除されてしまうことに注意

深い階層は、JsonNode の path(String) で対象ノードまで連結して JsonNode を求めてから
ObjectNode に cast して remove(String) を実行する。

上の、"B" ⇒ "B02" ⇒ "detail" を削除する場合、

((ObjectNode)(node.path("B").path("B02"))).remove("detail");

配列の1個を削除する場合は、JsonNode から ArrayNode に cast して
remove(int index) メソッドで削除する

((ArrayNode)(node.path("C"))).remove(1);

これら、

((ObjectNode)node).remove("D");
((ObjectNode)(node.path("B").path("B02"))).remove("detail");
((ArrayNode)(node.path("C"))).remove(1);

を実行した結果の JsonNode node は、以下になる。

{
  "A" : 123,
  "B" : {
    "B01" : "b01-01234",
    "B02" : {
      "name" : "orange"
    }
  },
  "C" : [ {
    "C01" : {
      "size" : 230
    }
  }, {
    "C03" : {
      "size" : 670
    }
  } ]
}