【Java】オブジェクトとJSON文字列の相互変換(外部ライブラリ未使用)

2022年2月28日

Javaでオブジェクトをインデント付きのJSON文字列に変換したい機会に遭遇したのでJSONを相互変換するクラスを作成しました。

わざわざ作らなくても外部ライブラリ使えば済む話なんですけどね。

主にテストを目的としており、JSON文字列にインデントを付ける機能を実装しています。
また、循環参照(自己参照のような形)であっても表示できるようにしています。

自分用に作ったものですが、使いたい方はご自由にお使いください。

JSONUtil.java
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import jdk.nashorn.api.scripting.ScriptObjectMirror;

public class JSONUtil {
	/**
	 * オブジェクトをJSON文字列に変換する
	 * @param data
	 * @return
	 */
	public static String toString(Object data){
		return toIndentString(data,"","", false, null);
	}

	/**
	 * オブジェクトをインデント付きJSON文字列に変換する
	 * @param data
	 * @return
	 */
	public static String toIndentString(Object data){
		return toIndentString(data,"\t","\n", false, null);
	}

	/**
	 * 指定したオブジェクトを決められたインデントを付加してJSON文字列に変換する
	 * @param data 変換対象のオブジェクト
	 * @param space オブジェクト開始インデント
	 * @param newLine オブジェクト終了インデント
	 * @param privateReflection オブジェクトをレフレクションする際privateフィールドも強制的に文字列に変換するか
	 * @param circularReferenceString 循環参照が発生した場合に表示する文字列。nullなら循環参照時に例外発生
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	public static String toIndentString(Object data, String space, String newLine, boolean privateReflection, String circularReferenceString){
		StringBuilder sb = new StringBuilder();
		List<Object> classRoot = new ArrayList<>();
		classRoot.add(data);

		int cnt = 0;
		if ( data instanceof String ) {
			sb.append("\""+data+"\"");
		} else if ( data instanceof Number || data instanceof Boolean ) {
			sb.append(data.toString());
		} else if (data == null) {
			sb.append("null");
		} else if ((data instanceof ScriptObjectMirror && ((ScriptObjectMirror)data).isArray())) {
			ScriptObjectMirror som = (ScriptObjectMirror)data;
			sb.append("["+newLine);
			for ( String key : som.keySet() ) {
				Object obj = som.get(key);
				cnt++;
				String fldJson = toJson(null, obj, space, newLine, space, (cnt < som.size()) ? "," : "", classRoot, privateReflection,circularReferenceString);
				sb.append(fldJson);
			}
			sb.append("]"+newLine);
		} else if ( data instanceof List ) {
			List l = (List)data;
			sb.append("["+newLine);
			for ( Object obj : l ) {
				cnt++;
				String fldJson = toJson(null, obj, space, newLine, space, (cnt < l.size()) ? "," : "", classRoot, privateReflection,circularReferenceString);
				sb.append(fldJson);
			}
			sb.append("]"+newLine);
		} else if ( data instanceof Map ) {
			Map m = (Map)data;
			sb.append("{"+newLine);
			for ( Object key : m.keySet() ) {
				cnt++;
				Object obj = m.get(key);
				String fldJson = toJson((String)key, obj, space, newLine, space, (cnt < m.size()) ? "," : "", classRoot, privateReflection,circularReferenceString);
				sb.append(fldJson);
			}
			sb.append("}"+newLine);
		} else {
			Class<?> cls = data.getClass();
			ArrayList<Field> arr = new ArrayList<>();
			while(cls != null && cls.getSuperclass() != null) {
				Field[] flds = cls.getDeclaredFields();
				for ( Field fld : flds ) arr.add(fld);
				cls = cls.getSuperclass();
			}
			sb.append("{"+newLine);
			for ( Field fld : arr ) {
				cnt++;
				if ( privateReflection ) fld.setAccessible(true);
				try {
					sb.append(toJson(fld.getName(), fld.get(data), space, newLine, space, (cnt < arr.size()) ? "," : "", classRoot, privateReflection,circularReferenceString));
				} catch (IllegalArgumentException | IllegalAccessException e) {
					throw new RuntimeException("Reflection fail.");
				}
			}
			sb.append("}"+newLine);
		}
		return sb.toString();
	}

	@SuppressWarnings("rawtypes")
	private static String toJson(String fieldName, Object value, String space,String newLine, String sr, String sep, List<Object> classRoot, boolean privateReflection, String circularReferenceString){
		if ( sr == null ) sr = "";
		StringBuilder sb = new StringBuilder();
		sb.append(sr);
		if ( fieldName != null ) sb.append("\""+fieldName+"\":");
		for ( Object o : classRoot ) {
			if ( o == value ) {
				if ( circularReferenceString == null ) throw new RuntimeException("Circular reference.");
				sb.append(circularReferenceString+sep+newLine);
				return sb.toString();
			}
		}
		classRoot.add(value);
		if ( value instanceof String ) {
			sb.append("\""+value+"\""+sep+newLine);
		} else if ( value instanceof Number || value instanceof Boolean ) {
			sb.append(value.toString()+sep+newLine);
		} else if (value == null) {
			sb.append("null"+sep+newLine);
		} else if ( (value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isArray())) {
			sb.append("["+newLine);
			int cnt = 0;
			ScriptObjectMirror som = (ScriptObjectMirror)value;
			for ( String key : som.keySet() ) {
				cnt++;
				Object obj = som.get(key);
				sb.append(toJson(null, obj, space, newLine, space + sr, (cnt < som.size()) ? "," : "", classRoot, privateReflection,circularReferenceString));
			}
			sb.append(sr+"]"+sep+newLine);
		} else if ( value instanceof List) {
			sb.append("["+newLine);
			int cnt = 0;
			List l = (List)value;
			for ( Object obj : (List)l ) {
				cnt++;
				sb.append(toJson(null, obj, space, newLine, space + sr, (cnt < l.size()) ? "," : "", classRoot, privateReflection,circularReferenceString));
			}
			sb.append(sr+"]"+sep+newLine);
		} else if ( value instanceof Map) {
			Map data = (Map)value;
			sb.append("{"+newLine);
			int cnt = 0;
			for ( Object k : data.keySet() ) {
				cnt++;
				Object obj = data.get(k);
				String key = (String)k;
				sb.append(toJson(key, obj, space, newLine, space + sr, (cnt < data.size()) ? "," : "", classRoot, privateReflection,circularReferenceString));
			}
			sb.append(sr+"}"+sep+newLine);
		} else {
			Class<?> cls = value.getClass();
			ArrayList<Field> arr = new ArrayList<>();
			while(cls != null && cls.getSuperclass() != null ) {
				Field[] flds = cls.getDeclaredFields();
				for ( Field fld : flds ) arr.add(fld);
				cls = cls.getSuperclass();
			}
			sb.append("{"+newLine);
			int cnt = 0;
			for ( Field fld : arr ) {
				cnt++;
				if ( privateReflection ) fld.setAccessible(true);
				else if ( Modifier.isPrivate(fld.getModifiers()) ) continue;
				try {
					sb.append(toJson(fld.getName(), fld.get(value), space, newLine, space + sr, (cnt < arr.size()) ? "," : "", classRoot, privateReflection, circularReferenceString));
				} catch (IllegalArgumentException | IllegalAccessException e) {
					throw new RuntimeException("Reflection fail.");
				}
			}
			sb.append(sr+"}"+sep+newLine);
		}
		classRoot.remove(classRoot.size() - 1);
		return sb.toString();
	}

	public static String stringToIndentString(String jsonString) throws ScriptException {
		return stringToIndentString(jsonString,"\t","\n", false, null);
	}

	/**
	 * JSON文字列をインデント付き文字列に変換します。
	 * @param jsonString
	 * @param space オブジェクト開始インデント
	 * @param newLine オブジェクト終了インデント
	 * @param privateReflection オブジェクトをレフレクションする際privateフィールドも強制的に文字列に変換するか
	 * @param circularReferenceString 循環参照が発生した場合に表示する文字列。nullなら循環参照時に例外発生
	 * @return
	 * @throws ScriptException
	 */
	public static String stringToIndentString(String jsonString, String space, String newLine, boolean privateReflection, String circularReferenceString) throws ScriptException {

        return toIndentString(toObject(jsonString), space, newLine, privateReflection,circularReferenceString);
	}

	/**
	 * JSON文字列をScriptObjectMirror型に変換します。
	 * ※Map<String,Object>としても使えます。
	 * @param jsonString
	 * @return
	 * @throws ScriptException
	 */
	public static Object toObject(String jsonString) throws ScriptException {

		// JSON文字列をオブジェクトに変換
		ScriptEngineManager manager = new ScriptEngineManager();
		ScriptEngine engine = manager.getEngineByName("javascript");

		return engine.eval("var a = "+jsonString+";a");
	}
}

テストサンプル1
import javax.script.ScriptException;

public class Test {
	public static void main(String[] args) throws ScriptException {
		//JSON文字列を正規化(インデントの付加)
		String res = JSONUtil.stringToIndentString("{\"a\":[12,34],\"b\":\"あいうえお\"}");
		System.out.println(res);
	}
}
実行結果

{ "a":[ 12, 34 ], "b":"あいうえお" }


テストサンプル2
import java.util.Map;

import javax.script.ScriptException;

public class Test {
	public static void main(String[] args) throws ScriptException {
		//JSON文字列をオブジェクトに変換
		Map obj = (Map) JSONUtil.toObject("{\"a\":[12,34],\"b\":\"あいうえお\"}");
		System.out.println(((Map)obj.get("a")).get("1"));
		System.out.println(obj.get("b"));
	}
}
実行結果

34
あいうえお


テストサンプル3
import javax.script.ScriptException;

public class Test {
	public static void main(String[] args) throws ScriptException {
		//オブジェクトをJSON文字列化
		Mario m = new Mario();
		Peach p = new Peach();
		Params param = new Params();
		param.m = m;
		param.p = p;
		m.marriage = p;
		p.marriage = m;
		String res = JSONUtil.toIndentString(param,"☆☆","\n",true, "[循環参照]");
		System.out.println(res);
	}
}

class Mario extends Human{
	private String a="まりお";
}
class Peach extends Human{
	String b="ぴーち";
	{
		man = false;
	}
}
class Human{
	boolean man=true;
	Human marriage;
}
class Params {
	Mario m;
	Peach p;
}
実行結果

{
☆☆”m”:{
☆☆☆☆”a”:”まりお”,
☆☆☆☆”man”:true,
☆☆☆☆”marriage”:{
☆☆☆☆☆☆”b”:”ぴーち”,
☆☆☆☆☆☆”man”:false,
☆☆☆☆☆☆”marriage”:[循環参照]
☆☆☆☆}
☆☆},
☆☆”p”:{
☆☆☆☆”b”:”ぴーち”,
☆☆☆☆”man”:false,
☆☆☆☆”marriage”:{
☆☆☆☆☆☆”a”:”まりお”,
☆☆☆☆☆☆”man”:true,
☆☆☆☆☆☆”marriage”:[循環参照]
☆☆☆☆}
☆☆}
}

2022/02/28追記
※最近のJavaではScriptEngineの利用ができなかったので、全部自力で書いたバージョンも公開しています。


あとから使えなくするのやめてほしー・・・
いい加減代替手段標準ライブラリで用意してほしー・・・

Java

Posted by nompor