Javaでクォートなしの構造化文字列を解析する方法

本稿では、クォートなしの構造化文字列(例: {Name:Heal,Age:20,Tag:[Coding,Reading]})をJavaで解析するための実装手法を解説します。標準JSONライブラリでは対応できないケースに特化したパーサーを実装します。

解析対象の文字列は以下の制約を満たす必要があります:

  • キーと値の区切りに「:」を使用
  • 要素の区切りに「,」を使用
  • ネストしたオブジェクトには「{」/「}」または「[」/「]」を適切に使用
  • 文字列内部に無効な記号が含まれない

以下に実装例を示します。パーサーはスタックを活用し、ネスト構造を再帰的に解析します。

package com.example.parser;

import java.util.*;

public class UnquotedStructParser {

    public static void main(String[] args) {
        String rawData = "[[{" +
                "Type:1," +
                "StoragePath:[{Path:/img/2023-01-01/1.jpg,Device:440112}," +
                "Size:140" +
                "}," +
                "{" +
                "Type:2,Path:images/2023-01-01/2.jpg," +
                "Nested:{ID:44011200}," +
                "Tags:[Java,Programming]," +
                "Width:200" +
                "}}]]";

        Object parsed = parseStruct(rawData);
        System.out.println("解析結果: " + parsed);
    }

    public static Object parseStruct(String input) {
        String cleanInput = trimBrackets(input);
        if (cleanInput.isEmpty()) return input;

        Stack<Character> leftStack = new Stack<>();
        Stack<Character> rightStack = new Stack<>();

        if (isBracketPair(cleanInput)) {
            leftStack.push(cleanInput.charAt(0));
            rightStack.push(cleanInput.charAt(cleanInput.length() - 1));
            cleanInput = cleanInput.substring(1, cleanInput.length() - 1);
        } else {
            throw new IllegalArgumentException("不正な構造の文字列: " + input);
        }

        List<Object> elementList = new ArrayList<>();
        Map<String, Object> keyValuePairs = new HashMap<>(16);

        processElements(cleanInput, leftStack, rightStack, elementList, keyValuePairs);
        return !elementList.isEmpty() ? elementList : keyValuePairs;
    }

    private static void processElements(String data, Stack<Character> leftStack, Stack<Character> rightStack,
                                      List<Object> elements, Map<String, Object> pairs) {
        if (data.isEmpty()) return;

        String[] tokens = data.split(",", -1);
        for (String token : tokens) {
            int colonIndex = token.indexOf(':');
            if (colonIndex > 0) {
                String key = token.substring(0, colonIndex);
                String value = token.substring(colonIndex + 1);

                if (value.startsWith("[")) {
                    int endBracket = findClosingBracket(data, '[');
                    List<Object> nestedList = (List<Object>) parseStruct(data.substring(colonIndex + 1, endBracket + 1));
                    pairs.put(key, nestedList);
                    data = data.substring(endBracket + 1).replaceFirst("^,", "");
                    processElements(data, leftStack, rightStack, elements, pairs);
                    break;
                } else if (value.startsWith("{")) {
                    int endBrace = findClosingBracket(data, '{');
                    Map<String, Object> nestedMap = (Map<String, Object>) parseStruct(data.substring(colonIndex + 1, endBrace + 1));
                    pairs.put(key, nestedMap);
                    data = data.substring(endBrace + 1).replaceFirst("^,", "");
                    processElements(data, leftStack, rightStack, elements, pairs);
                    break;
                } else {
                    pairs.put(key, value);
                    data = data.substring(colonIndex + 1 + value.length() + 1).replaceFirst("^,", "");
                }
            } else {
                elements.add(token);
            }
        }

        while (!leftStack.isEmpty()) {
            if (leftStack.peek() == '{' && !data.isEmpty() && data.charAt(0) == '}') {
                leftStack.pop();
                data = data.substring(1);
            } else {
                throw new IllegalArgumentException("不整合な構造: " + data);
            }
        }
    }

    private static boolean isBracketPair(String str) {
        return (str.startsWith("{") && str.endsWith("}")) || 
               (str.startsWith("[") && str.endsWith("]"));
    }

    private static String trimBrackets(String str) {
        while (isBracketPair(str)) {
            str = str.substring(1, str.length() - 1);
        }
        return str;
    }

    private static int findClosingBracket(String str, char openBracket) {
        int count = 0;
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == openBracket) count++;
            else if (str.charAt(i) == (openBracket == '[' ? ']' : '}')) {
                count--;
                if (count == 0) return i;
            }
        }
        throw new IllegalArgumentException("不整合な括弧");
    }
}

実行例の出力結果:

解析結果: [[{Type=1, StoragePath=[{Path=/img/2023-01-01/1.jpg, Device=440112}, {Size=140}], Width=200}, {Type=2, Path=images/2023-01-01/2.jpg, Nested={ID=44011200}, Tags=[Java, Programming], Width=200}]]

タグ: Java string-parsing json-serialization stack-algorithm

6月6日 21:33 投稿