package org.jcodec.common.tools; import java.util.Iterator; import org.jcodec.common.IntArrayList; import org.jcodec.common.io.NIOUtils; import org.jcodec.platform.Platform; import java.lang.IllegalArgumentException; import java.lang.NullPointerException; import java.lang.StringBuilder; import java.lang.System; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Simple JSON serializer, introduced because jcodec can not use dependencies as * they bring frastration on some platforms * * @author The JCodec project */ public class ToJSON { static Set<Class> primitive = new HashSet<Class>(); static Set<String> omitMethods = new HashSet<String>(); static { primitive.add(Boolean.class); primitive.add(Byte.class); primitive.add(Short.class); primitive.add(Integer.class); primitive.add(Long.class); primitive.add(Float.class); primitive.add(Double.class); primitive.add(Character.class); } static { omitMethods.add("getClass"); omitMethods.add("get"); } public static List<String> allFields(Class claz) { return allFieldsExcept(claz, new String[]{}); } public static List<String> allFieldsExcept(Class claz, String[] except) { List<String> result = new ArrayList<String>(); for (Method method : Platform.getDeclaredMethods(claz)) { if (!isGetter(method)) continue; try { String name = toName(method); result.add(name); } catch (Exception e) { } } return result; } /** * Converts an object to JSON * * @param obj * @return */ public static String toJSON(Object obj) { StringBuilder builder = new StringBuilder(); IntArrayList stack = IntArrayList.createIntArrayList(); toJSONSub(obj, stack, builder); return builder.toString(); } /** * Converts specified fields of an object to JSON * * Useful because it doesn't enclose the field list into JSON object * brackets "{}" leaving flexibility to append any other information. Makes * it possible to specify only selected fields. * * @param obj * @param builder * @param fields */ public static void fieldsToJSON(Object obj, StringBuilder builder, String[] fields) { Method[] methods = Platform.getMethods(obj.getClass()); for (String field : fields) { Method m = findGetter(methods, field); if (m == null) continue; invoke(obj, IntArrayList.createIntArrayList(), builder, m, field); } } private static Method findGetter(Method[] methods, String field) { String isGetter = getterName("is", field); String getGetter = getterName("get", field); for (Method method : methods) { if ((isGetter.equals(method.getName()) || getGetter.equals(method.getName())) && isGetter(method)) return method; } return null; } private static String getterName(String pref, String field) { if (field == null) throw new NullPointerException("Passed null string as field name"); char[] ch = field.toCharArray(); if (ch.length == 0) return pref; if (ch.length > 1 && Character.isUpperCase(ch[1])) ch[0] = Character.toLowerCase(ch[0]); else ch[0] = Character.toUpperCase(ch[0]); return pref + new String(ch); } private static void toJSONSub(Object obj, IntArrayList stack, StringBuilder builder) { if(obj == null) { builder.append("null"); return; } String className = obj.getClass().getName(); if(className.startsWith("java.lang") && !className.equals("java.lang.String")) { builder.append("null"); return; } int id = System.identityHashCode(obj); if (stack.contains(id)) { builder.append("null"); return; } stack.push(id); if (obj instanceof ByteBuffer) obj = NIOUtils.toArray((ByteBuffer) obj); if (obj == null) { builder.append("null"); } else if (obj instanceof String) { builder.append("\""); escape((String) obj, builder); builder.append("\""); } else if (obj instanceof Map) { Iterator it = ((Map) obj).entrySet().iterator(); builder.append("{"); while (it.hasNext()) { Map.Entry e = (Map.Entry) it.next(); builder.append("\""); builder.append(e.getKey()); builder.append("\":"); toJSONSub(e.getValue(), stack, builder); if (it.hasNext()) builder.append(","); } builder.append("}"); } else if (obj instanceof Iterable) { Iterator it = ((Iterable) obj).iterator(); builder.append("["); while (it.hasNext()) { toJSONSub(it.next(), stack, builder); if (it.hasNext()) builder.append(","); } builder.append("]"); } else if (obj instanceof Object[]) { builder.append("["); int len = Array.getLength(obj); for (int i = 0; i < len; i++) { toJSONSub(Array.get(obj, i), stack, builder); if (i < len - 1) builder.append(","); } builder.append("]"); } else if (obj instanceof long[]) { long[] a = (long[]) obj; builder.append("["); for (int i = 0; i < a.length; i++) { builder.append(String.format("0x%016x", a[i])); if (i < a.length - 1) builder.append(","); } builder.append("]"); } else if (obj instanceof int[]) { int[] a = (int[]) obj; builder.append("["); for (int i = 0; i < a.length; i++) { builder.append(String.format("0x%08x", a[i])); if (i < a.length - 1) builder.append(","); } builder.append("]"); } else if (obj instanceof float[]) { float[] a = (float[]) obj; builder.append("["); for (int i = 0; i < a.length; i++) { builder.append(String.format("%.3f", a[i])); if (i < a.length - 1) builder.append(","); } builder.append("]"); } else if (obj instanceof double[]) { double[] a = (double[]) obj; builder.append("["); for (int i = 0; i < a.length; i++) { builder.append(String.format("%.6f", a[i])); if (i < a.length - 1) builder.append(","); } builder.append("]"); } else if (obj instanceof short[]) { short[] a = (short[]) obj; builder.append("["); for (int i = 0; i < a.length; i++) { builder.append(String.format("0x%04x", a[i])); if (i < a.length - 1) builder.append(","); } builder.append("]"); } else if (obj instanceof byte[]) { byte[] a = (byte[]) obj; builder.append("["); for (int i = 0; i < a.length; i++) { builder.append(String.format("0x%02x", a[i])); if (i < a.length - 1) builder.append(","); } builder.append("]"); } else if (obj instanceof boolean[]) { boolean[] a = (boolean[]) obj; builder.append("["); for (int i = 0; i < a.length; i++) { builder.append(a[i]); if (i < a.length - 1) builder.append(","); } builder.append("]"); } else if(obj.getClass().isEnum()) { builder.append(String.valueOf(obj)); } else { builder.append("{"); for (Method method : Platform.getMethods(obj.getClass())) { if (omitMethods.contains(method.getName()) || !isGetter(method)) continue; String name = toName(method); invoke(obj, stack, builder, method, name); } builder.append("}"); } stack.pop(); } private static void invoke(Object obj, IntArrayList stack, StringBuilder builder, Method method, String name) { try { Object invoke = method.invoke(obj); builder.append('"'); builder.append(name); builder.append("\":"); if (invoke != null && primitive.contains(invoke.getClass())) builder.append(invoke); else toJSONSub(invoke, stack, builder); builder.append(","); // } } catch (Exception e) { } } private static void escape(String invoke, StringBuilder sb) { char[] ch = invoke.toCharArray(); for (char c : ch) { if (c < 0x20) sb.append(String.format("\\%02x", (int) c)); else sb.append(c); } } private static String toName(Method method) { if (!isGetter(method)) throw new IllegalArgumentException("Not a getter"); char[] name = method.getName().toCharArray(); int ind = name[0] == 'g' ? 3 : 2; name[ind] = Character.toLowerCase(name[ind]); return new String(name, ind, name.length - ind); } public static boolean isGetter(Method method) { if (!Modifier.isPublic(method.getModifiers())) return false; if (!method.getName().startsWith("get") && !(method.getName().startsWith("is") && method.getReturnType() == Boolean.TYPE)) return false; if (method.getParameterTypes().length != 0) return false; // if (void.class.equals(method.getReturnType())) // return false; return true; } }