本稿では、クォートなしの構造化文字列(例: {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}]]