json-serialiser
Details
.gitignore 34(+34 -0)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4a482b8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+# ignore eclipse project files
+.project
+.classpath
+
+# ignore Intellij Idea project files
+.idea
+*.iml
+
+# Netbeans
+/nbproject/
+/nbactions.xml
+
+# Netbeans deploy
+/build/
+
+# Maven deploy
+/target/
+
+# Ant deploy
+/dist/
+
+# Class files
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
pom.xml 13(+13 -0)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..29dd961
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>br.ufrgs.inf.prosoft.jsonserialiser</groupId>
+ <artifactId>JSONSerialiser</artifactId>
+ <version>1.0</version>
+ <packaging>jar</packaging>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ </properties>
+</project>
\ No newline at end of file
diff --git a/src/main/java/br/ufrgs/inf/prosoft/jsonserialiser/JSONSerialiser.java b/src/main/java/br/ufrgs/inf/prosoft/jsonserialiser/JSONSerialiser.java
new file mode 100644
index 0000000..ad5acfa
--- /dev/null
+++ b/src/main/java/br/ufrgs/inf/prosoft/jsonserialiser/JSONSerialiser.java
@@ -0,0 +1,369 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package br.ufrgs.inf.prosoft.jsonserialiser;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * @author romulo
+ */
+public class JSONSerialiser {
+
+ private static final Collection<Object> VISITED = new ArrayList<>();
+ private static Writer WRITER;
+
+ private static class StringBuilderWriter extends Writer {
+
+ private final StringBuilder stringBuilder;
+
+ public StringBuilderWriter(StringBuilder stringBuilder) {
+ this.stringBuilder = stringBuilder;
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) {
+ stringBuilder.append(cbuf, off, len);
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ }
+
+ public static void serialise(Object bean, Writer writer, boolean cyclicFields) throws IOException {
+ JSONSerialiser.WRITER = writer;
+ serialiseBean(bean, cyclicFields);
+ JSONSerialiser.WRITER = null;
+ }
+
+ public static void serialise(Object bean, Writer writer) throws IOException {
+ serialise(bean, writer, true);
+ }
+
+ public static String serialise(Object bean, boolean cyclicFields) {
+ StringBuilder stringBuilder = new StringBuilder();
+ try {
+ serialise(bean, new StringBuilderWriter(stringBuilder), cyclicFields);
+ } catch (IOException ex) {
+ }
+ return stringBuilder.toString();
+ }
+
+ public static String serialise(Object bean) {
+ return serialise(bean, true);
+ }
+
+ public static String serialiseCylicObject(Object bean) {
+ resetCycleBreaker();
+ String serialise = serialise(bean);
+ resetCycleBreaker();
+ return serialise;
+ }
+
+ public static String serialiseAcylicObject(Object bean) {
+ resetCycleBreaker();
+ String serialise = serialise(bean, false);
+ resetCycleBreaker();
+ return serialise;
+ }
+
+ public static void serialiseAcylicObject(Object bean, Writer writer) throws IOException {
+ resetCycleBreaker();
+ serialise(bean, writer, false);
+ resetCycleBreaker();
+ }
+
+ public static void resetCycleBreaker() {
+ JSONSerialiser.VISITED.clear();
+ }
+
+ private static void serialiseBean(Object bean, boolean cyclicFields) throws IOException {
+ if (bean == null) {
+ JSONSerialiser.WRITER.append("null");
+ return;
+ }
+ Class<?> klass = bean.getClass();
+ List<Field> fields = new ArrayList<>();
+ fields = getAllFields(fields, klass);
+ JSONSerialiser.WRITER.append("{");
+ for (Iterator<Field> it = fields.iterator(); it.hasNext();) {
+ Field field = it.next();
+ try {
+ field.setAccessible(true);
+ Object result = field.get(bean);
+ if (result == null) {
+ continue;
+ }
+ if (!cyclicFields) {
+ resetCycleBreaker();
+ }
+ JSONSerialiser.WRITER.append("\"").append(field.getName()).append("\"");
+ JSONSerialiser.WRITER.append(":");
+ wrap(result);
+ } catch (Exception ignore) {
+ JSONSerialiser.WRITER.append("\"").append("JSON-FIELD-EXCEPTION").append("\"");
+ System.err.println("[JSON-trace] field exception: " + ignore);
+ }
+ if (it.hasNext()) {
+ JSONSerialiser.WRITER.append(",");
+ }
+ }
+ JSONSerialiser.WRITER.append("}");
+ }
+
+ private static void serialiseBean(Object bean) throws IOException {
+ serialiseBean(bean, true);
+ }
+
+ private static List<Field> getAllFields(List<Field> fields, Class<?> type) {
+ for (Field field : type.getDeclaredFields()) {
+ if (!field.getName().startsWith("ajc$tjp_")) {
+ fields.add(field);
+ }
+ }
+ if (type.getSuperclass() != null) {
+ getAllFields(fields, type.getSuperclass());
+ }
+ return fields;
+ }
+
+ private static void wrap(Object object) throws IOException {
+ try {
+ if (object == null) {
+ JSONSerialiser.WRITER.append("null");
+ return;
+ }
+ if (object instanceof Boolean) {
+ JSONSerialiser.WRITER.append("\"").append(String.valueOf(object)).append("\"");
+ return;
+ }
+ if (object instanceof Character) {
+ serialiseString(String.valueOf(object));
+ return;
+ }
+ if (object instanceof String) {
+ String string = (String) object;
+ serialiseString(string);
+ return;
+ }
+ if (object instanceof Number) {
+ Number number = (Number) object;
+ serialiseNumber(number);
+ return;
+ }
+ if (object instanceof Enum) {
+ serialiseString(((Enum<?>) object).name());
+ return;
+ }
+ if (object instanceof Collection) {
+ Collection<?> collection = (Collection<?>) object;
+ serialiseCollection(collection);
+ return;
+ }
+ if (object.getClass().isArray()) {
+ serialiseArray(object);
+ return;
+ }
+ if (object instanceof Map) {
+ Map<?, ?> map = (Map<?, ?>) object;
+ serialiseMap(map);
+ return;
+ }
+ if (VISITED.stream().anyMatch((visited) -> (visited == object))) {
+ try {
+ JSONSerialiser.WRITER.append("\"").append(String.valueOf(object)).append("\"");
+ } catch (Exception e) {
+ JSONSerialiser.WRITER.append("\"").append(object.getClass().getName()).append("\"");
+ } finally {
+ return;
+ }
+ }
+ VISITED.add(object);
+ serialiseBean(object);
+ } catch (ConcurrentModificationException exception) {
+ System.err.println("[JSONSerialiser] ConcurrentModificationException");
+ } catch (Exception exception) {
+ System.err.println("[JSON-java] serialise exception: " + exception);
+ JSONSerialiser.WRITER.append("\"").append("JSON_SERIALISE_EXCEPTION").append("\"");
+ exception.printStackTrace();
+ }
+ }
+
+ private static void serialiseMap(Map map) throws IOException {
+ try {
+ map.keySet().iterator();
+ } catch (Exception ex) {
+ return;
+ }
+ JSONSerialiser.WRITER.append("{");
+ for (Iterator<?> it = map.keySet().iterator(); it.hasNext();) {
+ Object key = it.next();
+ if (key == null || map.get(key) == null) {
+ continue;
+ }
+ if (!(key instanceof String)) {
+ JSONSerialiser.WRITER.append("\"");
+ wrap(key);
+ JSONSerialiser.WRITER.append("\"");
+ } else {
+ wrap(key);
+ }
+ JSONSerialiser.WRITER.append(":");
+ wrap(map.get(key));
+ if (it.hasNext()) {
+ JSONSerialiser.WRITER.append(",");
+ }
+ }
+ JSONSerialiser.WRITER.append("}");
+ }
+
+ private static void serialiseArray(Object array) throws IOException {
+ JSONSerialiser.WRITER.append("[");
+ int length = Array.getLength(array);
+ for (int i = 0; i < length;) {
+ Object object = Array.get(array, i);
+ wrap(object);
+ if (++i < length) {
+ JSONSerialiser.WRITER.append(",");
+ }
+ }
+ JSONSerialiser.WRITER.append("]");
+ }
+
+ private static void serialiseCollection(Collection collection) throws IOException {
+ try {
+ collection.iterator();
+ } catch (Exception ex) {
+ return;
+ }
+ JSONSerialiser.WRITER.append("[");
+ for (Iterator it = collection.iterator(); it.hasNext();) {
+ Object object = it.next();
+ wrap(object);
+ if (it.hasNext()) {
+ JSONSerialiser.WRITER.append(",");
+ }
+ }
+ JSONSerialiser.WRITER.append("]");
+ }
+
+ private static void serialiseString(String string) throws IOException {
+ if (string == null || string.isEmpty()) {
+ JSONSerialiser.WRITER.append("\"\"");
+ return;
+ }
+
+ char b;
+ char c = 0;
+ String hexadecimal;
+ int i;
+ int len = string.length();
+
+ JSONSerialiser.WRITER.append('"');
+ for (i = 0; i < len; i++) {
+ b = c;
+ c = string.charAt(i);
+ switch (c) {
+ case '\\':
+ case '"':
+ JSONSerialiser.WRITER.append('\\');
+ JSONSerialiser.WRITER.append(c);
+ break;
+ case '/':
+ if (b == '<') {
+ JSONSerialiser.WRITER.append('\\');
+ }
+ JSONSerialiser.WRITER.append(c);
+ break;
+ case '\b':
+ JSONSerialiser.WRITER.append("\\b");
+ break;
+ case '\t':
+ JSONSerialiser.WRITER.append("\\t");
+ break;
+ case '\n':
+ JSONSerialiser.WRITER.append("\\n");
+ break;
+ case '\f':
+ JSONSerialiser.WRITER.append("\\f");
+ break;
+ case '\r':
+ JSONSerialiser.WRITER.append("\\r");
+ break;
+ default:
+ if (c < ' ' || (c >= '\u0080' && c < '\u00a0')
+ || (c >= '\u2000' && c < '\u2100')) {
+ JSONSerialiser.WRITER.append("\\u");
+ hexadecimal = Integer.toHexString(c);
+ JSONSerialiser.WRITER.append("0000", 0, 4 - hexadecimal.length());
+ JSONSerialiser.WRITER.append(hexadecimal);
+ } else {
+ JSONSerialiser.WRITER.append(c);
+ }
+ }
+ }
+ JSONSerialiser.WRITER.append('"');
+ }
+
+ private static void serialiseNumber(Number number) throws IOException {
+ // not all Numbers may match actual JSON Numbers. i.e. Fractions or Complex
+ final String numberAsString = numberToString(number);
+ try {
+ // Use the BigDecimal constructor for it's parser to validate the format.
+ @SuppressWarnings("unused")
+ BigDecimal unused = new BigDecimal(numberAsString);
+ // Close enough to a JSON number that we will return it unquoted
+ JSONSerialiser.WRITER.append(numberAsString);
+ } catch (NumberFormatException ex) {
+ // The Number value is not a valid JSON number.
+ // Instead we will quote it as a string
+ serialiseString(numberAsString);
+ }
+
+ }
+
+ private static String numberToString(Number number) {
+ if (number instanceof Double) {
+ if (((Double) number).isInfinite() || ((Double) number).isNaN()) {
+ return String.valueOf(number);
+ }
+ } else if (number instanceof Float) {
+ if (((Float) number).isInfinite() || ((Float) number).isNaN()) {
+ return String.valueOf(number);
+ }
+ }
+
+ // Shave off trailing zeros and decimal point, if possible.
+ String string = number.toString();
+ if (string.indexOf('.') > 0 && string.indexOf('e') < 0
+ && string.indexOf('E') < 0) {
+ while (string.endsWith("0")) {
+ string = string.substring(0, string.length() - 1);
+ }
+ if (string.endsWith(".")) {
+ string = string.substring(0, string.length() - 1);
+ }
+ }
+ return string;
+ }
+
+}