JavaでJSON変換しようとしたらScriptEngineが動かなくなった

前にScriptEngineクラスを使用し、JSON文字列をJavaオブジェクトに変換するコードを紹介しましたが、なんかJava17で使えなくなってました。

ムカついたので自力で相互変換できるクラス作っときました。

必要であればテストなどで、ご利用ください。

適当に作ったのでテスト以外での使用は非推奨です。

JSONUtil.java
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JSONUtil {

	private static class TokenList{
		private LinkedList<String> tokens = new LinkedList<>();
		private boolean isBlankSkip;
		private boolean isSkipMode;
		public boolean isNext(String s){
			return isNext(s,0);
		}
		public boolean isNext(String s, int idx){
			if (isBlankSkip && !isSkipMode) blankTokenSkip(idx);
			return isIndexExists(idx) ? false : s.equals(tokens.get(idx));
		}
		public String next() {
			return next(0);
		}
		public String next(int idx) {
			if (isBlankSkip && !isSkipMode) blankTokenSkip(idx);
			return isIndexExists(idx) ? null : tokens.remove(idx);
		}
		public String last() {
			return tokens.removeLast();
		}
		public String getNext() {
			return getNext(0);
		}
		public String getNext(int idx) {
			if (isBlankSkip && !isSkipMode) blankTokenSkip(idx);
			return isIndexExists(idx) ? null : tokens.get(idx);
		}
		private boolean isIndexExists(int idx) {
			return tokens.size()<=idx;
		}
		public boolean isEmpty() {
			return tokens.isEmpty();
		}
		public void add(String s) {
			tokens.add(s);
		}
		public void addFirst(String s) {
			tokens.addFirst(s);
		}
		public Iterator<String> iterator(){
			return tokens.iterator();
		}
		public String getLast() {
			return tokens.getLast().toString();
		}
		public String getFirst() {
			return tokens.getFirst().toString();
		}
		public String removeLast() {
			return tokens.removeLast().toString();
		}
		public String removeFirst() {
			return tokens.removeFirst().toString();
		}
		public void clear() {
			tokens.clear();
		}
		public String toString() {
			return tokens.toString();
		}
		private void blankTokenSkip(int idx) {
			isSkipMode=true;
			try {
				for(int i = 0;!this.isEmpty()&&i<=idx;) {
					String next = this.getNext(i);
					if ( next == null ) return;
					switch(next) {
					case "\t":this.next(i);break;
					case " ":this.next(i);break;
					case "\n":this.next(i);break;
					case "\r":this.next(i);break;
					default:i++;continue;
					}
				}
			}finally {
				isSkipMode=false;
			}
		}
		public void setBlankSkip(boolean b) {
			isBlankSkip = b;
		}
		public boolean isBlankSkip() {
			return isBlankSkip;
		}
	}

	public static String escapeJsonValue(String target){
		target = target.replace("\\", "\\\\");
		target = target.replace("\"", "\\\"");
		target = target.replace("\n", "\\n");
		target = target.replace("\t", "\\t");
		return target;
	}
	
	public static <T> T toObject(String json) {

		String regex = "\\s|,|\\{|\\\\|}|:|\\[|]|\\\"|(?<=\\\\)[nt]|[^,\\{\\\\}:\\[\\]\\\"\\s]+";
		Pattern p = Pattern.compile(regex);
		Matcher m = p.matcher(json);
		TokenList tokens = new TokenList();
		while (m.find()){
			String tx = m.group();
			tokens.add(tx);
		}
		tokens.setBlankSkip(true);
		T result = null;
		try {

			switch ( tokens.getNext() ) {
			case "{":tokens.next();result=(T) parseJsonObject(tokens);break;
			case "[":tokens.next();result=(T) parseJsonList(tokens);break;
			case "\"":tokens.next();result=(T) parseJsonText(tokens);break;
			default:result=(T) parseJsonValue(tokens.next());break;
			}

		} catch ( Exception e ) {
			IllegalArgumentException ex = new IllegalArgumentException("このJSONの解析には対応していません。"+json);
			ex.initCause(e);
			throw ex;
		}
		return result;
	}

	private static Map<String, Object> parseJsonObject(TokenList tokens){

		Map<String, Object> map = new LinkedHashMap<String, Object>();
		String key = null;
		boolean isKeyMode = true;
		boolean end = false;

		loop:
		while ( !tokens.isEmpty() ) {
			String token = tokens.next();
			switch ( token ) {
			case "[":
				if ( end ) throw new IllegalArgumentException();
				if ( isKeyMode ) throw new IllegalArgumentException();
				List<Object> list = parseJsonList(tokens);
				map.put(key, list);
				key = null;
				end = true;
				break;
			case "\"":
				if ( end ) throw new IllegalArgumentException();
				String tx = parseJsonText(tokens);
				if ( isKeyMode ) {
					key = tx;
					isKeyMode = false;
				} else {
					map.put(key, tx);
					key = null;
					end = true;
				}
				break;
			case "{":
				if ( end ) throw new IllegalArgumentException();
				if ( isKeyMode ) throw new IllegalArgumentException();
				Map<String, Object> inMap = parseJsonObject(tokens);
				map.put(key, inMap);
				key = null;
				end = true;
				break;
			case "}":
				break loop;
			case ":":
				if ( end ) throw new IllegalArgumentException();
				if ( isKeyMode ) throw new IllegalArgumentException();
				isKeyMode = false;
				break;
			case ",":
				end = false;
				isKeyMode = true;
				break;
			case "\t":
			case " ":
			case " ":
			case "\r":
			case "\n":
				break;
			default:
				if ( end ) throw new IllegalArgumentException();
				if ( isKeyMode ) throw new IllegalArgumentException();
				map.put(key, parseJsonValue(token));
				key = null;
				end = true;
				break;
			}
		}

		return map;
	}


	private static List<Object> parseJsonList(TokenList tokens){

		List<Object> list = new ArrayList<>();
		boolean end = false;

		loop:
		while ( !tokens.isEmpty() ) {
			String token = tokens.next();
			switch ( token ) {
			case "[":
				if ( end ) throw new IllegalArgumentException();
				list.add(parseJsonList(tokens));
				end = true;
				break;
			case "\"":
				if ( end ) throw new IllegalArgumentException();
				list.add(parseJsonText(tokens));
				end = true;
				break;
			case "{":
				if ( end ) throw new IllegalArgumentException();
				list.add(parseJsonObject(tokens));
				end = true;
				break;
			case "]":
				break loop;
			case ",":
				end = false;
				break;
			case "\t":
				break;
			case "\n":
				break;
			default:
				if ( end ) throw new IllegalArgumentException();
				list.add(parseJsonValue(token));
				end = true;
				break;
			}
		}

		return list;
	}

	private static Object parseJsonValue(String token) {

		try {
			return new BigDecimal(token);
		}catch(Exception e) {
			if ( "true".equals(token) ){
				return true;
			}
			if ( "false".equals(token) ){
				return false;
			}
			if ( "null".equals(token) ){
				return null;
			}
			throw new IllegalArgumentException("Json文字列不正。");
		}
	}

	private static String parseJsonText(TokenList tokens){
		String s = "";
		boolean isBlankSkip = tokens.isBlankSkip();
		tokens.setBlankSkip(false);
		while ( !tokens.isEmpty() ) {
			if ( tokens.isNext("\"") ) {
				tokens.next();
				break;
			}
			if ( tokens.isNext("\\") ) {
				tokens.next();
				if ( tokens.isNext("n") ) {
					s += "\n";
					tokens.next();
				} else if ( tokens.isNext("t") ) {
					s += "\t";
					tokens.next();
				} else {
					s += tokens.next();
				}
			} else {
				s += tokens.next();
			}
		}
		tokens.setBlankSkip(isBlankSkip);
		return s;
	}

	public static <T> String toString(T obj){
		return toString(obj, "");
	}

	public static <T> String toString(T obj, String indent){
		return toJsonString(null, obj, "", "", "\n", indent);
	}
	@SuppressWarnings("rawtypes")
	private static String toJsonString(String fieldName, Object value, String indent, String sep, String defNewLine, String defIndent){
		if ( indent == null ) indent = "";
		StringBuilder sb = new StringBuilder();
		sb.append(indent);
		sb.append(sep);
		if ( fieldName != null ) sb.append("\""+fieldName+"\":");
		if ( value instanceof String ) {
			sb.append("\""+escapeJsonValue(value.toString())+"\""+defNewLine);
		} else if ( value instanceof Number || value instanceof BigDecimal ) {
			sb.append(value.toString()+defNewLine);
		} else if ( value instanceof Boolean ) {
			sb.append(value.toString()+defNewLine);
		} else if ( value instanceof List) {
			sb.append("["+defNewLine);
			int cnt = 0;
			for ( Object obj : (List)value ) {
				sb.append(toJsonString(null, obj, indent + defIndent, (cnt != 0) ? "," : "", defNewLine, defIndent));
				cnt++;
			}
			sb.append(indent+"]"+defNewLine);
		} else if ( value instanceof Map) {
			Map data = (Map) value;
			sb.append("{"+defNewLine);
			int cnt = 0;
			for ( Object k : data.keySet() ) {
				Object obj = data.get(k);
				if ( k == null || !(k instanceof String) || obj == null) continue;
				String key = (String)k;
				sb.append(toJsonString(key, obj, indent + defIndent, (cnt != 0) ? "," : "", defNewLine, defIndent));
				cnt++;
			}
			sb.append(indent+"}"+defNewLine);
		} else if ( value == null ) {
			sb.append(value+defNewLine);
		} else {
			throw new IllegalArgumentException("String,Number,BigDecimal,List,Map以外の型を指定しようとしました。");
		}
		return sb.toString();
	}
}
テストコード
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
public class Test {
 
	public static void main(String[] args) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("A", "A");
		map.put("B", 100);
		map.put("C", List.of(1,5,2));
		map.put("D", null);
		String jstring = JSONUtil.toString(map, "\t");
		System.out.println(jstring);

		Map<String, Object> res = JSONUtil.toObject(jstring);
		System.out.println(res);
	}
}
実行結果
{
	"A":"A"
	,"B":100
	,"C":[
		1
		,5
		,2
	]
}

{A=A, B=100, C=[1, 5, 2]}

※前回はクラス変換機能が一部入っていましたが今回は入れてないです。

Java

Posted by nompor