/**
* This software is licensed to you under the Apache License, Version 2.0 (the
* "Apache License").
*
* LinkedIn's contributions are made under the Apache License. If you contribute
* to the Software, the contributions will be deemed to have been made under the
* Apache License, unless you expressly indicate otherwise. Please do not make any
* contributions that would be inconsistent with the Apache License.
*
* You may obtain a copy of the Apache License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, this software
* distributed under the Apache License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Apache
* License for the specific language governing permissions and limitations for the
* software governed under the Apache License.
*
* © 2012 LinkedIn Corp. All Rights Reserved.
*/
package com.senseidb.search.client.json;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.senseidb.search.client.ReflectionUtil;
public class JsonSerializer {
public static String NULL = "__NULL__";
public static Object serialize(Object object) {
try {
return serialize(object,true);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public static Object serialize(Object object, boolean handleCustomJsonHandler) throws JSONException {
return serialize(Collections.newSetFromMap(new IdentityHashMap()), object, handleCustomJsonHandler);
}
private static Object serialize(Set<Object> parents,
Object object,
boolean handleCustomJsonHandler) throws JSONException {
if (object == null) {
return null;
}
if (parents.contains(object)) {
// Loop reference
return null;
}
parents.add(object);
try {
if (object instanceof String || object instanceof Number || object instanceof Boolean || object.getClass().isPrimitive() || object instanceof JSONObject) {
return object;
}
CustomJsonHandler customJsonHandler = getCustomJsonHandlerByType(object.getClass());
if (customJsonHandler != null && handleCustomJsonHandler) {
JsonHandler jsonHandler = instantiate(customJsonHandler.value());
return jsonHandler.serialize(object);
}
if (object.getClass().isEnum()) {
return object.toString();
}
if (object instanceof Collection) {
Collection collection = (Collection) object;
List<Object> arr = new ArrayList<Object>(collection.size());
for(Object obj : collection) {
arr.add(serialize(parents, obj, true));
}
return new JSONArray(arr);
}
if (object instanceof Map) {
Map map = (Map) object;
JSONObject ret = new JSONObject();
for(Map.Entry entry : (Set<Map.Entry>) map.entrySet()) {
Object key = serialize(parents, entry.getKey(), true);
if (key == null) key = NULL;
else key = key.toString();
ret.put((String)key, serialize(parents, entry.getValue(), true));
}
return ret;
}
// serialize object by reflection
JSONObject ret = new JSONObject();
for (Field field : object.getClass().getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) continue;
field.setAccessible(true);
String name = field.getName();
if (field.isAnnotationPresent(JsonField.class)) {
name = field.getAnnotation(JsonField.class).value();
}
try {
CustomJsonHandler customJsonHandlerAnnotation = getCustomJsonHandlerByField(field);
Object fieldValue = field.get(object);
if (customJsonHandlerAnnotation == null) {
ret.put(name, serialize(parents, fieldValue, true));
} else {
JsonHandler jsonHandler = instantiate(customJsonHandlerAnnotation.value());
Object fieldJson = jsonHandler.serialize(fieldValue);
if (customJsonHandlerAnnotation.flatten() && fieldJson != null) {
String[] names = JSONObject.getNames((JSONObject)fieldJson);
if (names == null || names.length != 1) {
throw new IllegalStateException("It's impossible to flatten the JsonExpression " + fieldJson);
}
Object internalJson = ((JSONObject) fieldJson).opt(names[0]);
if (customJsonHandlerAnnotation.overrideColumnName()) {
name = names[0];
}
fieldJson = internalJson;
}
ret.put(name, fieldJson);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return ret;
}
finally
{
parents.remove(object);
}
}
private static Map<Class<?>, CustomJsonHandler> jsonHandlersByType = Collections.synchronizedMap(new WeakHashMap<Class<?>, CustomJsonHandler>());
private static Map<Field, CustomJsonHandler> jsonHandlersByField = Collections.synchronizedMap(new WeakHashMap<Field, CustomJsonHandler>());
private static Map<Class<? extends JsonHandler>, JsonHandler> jsonHandlers = Collections.synchronizedMap(new WeakHashMap<Class<? extends JsonHandler>, JsonHandler>());
private static CustomJsonHandler getCustomJsonHandlerByType(Class<?> cls) {
if (!jsonHandlersByType.containsKey(cls)) {
CustomJsonHandler customJsonHandler = (CustomJsonHandler) ReflectionUtil.getAnnotation(cls, CustomJsonHandler.class);
jsonHandlersByType.put(cls, customJsonHandler);
}
return jsonHandlersByType.get(cls);
}
private static CustomJsonHandler getCustomJsonHandlerByField(Field field) {
if (!jsonHandlersByField.containsKey(field)) {
CustomJsonHandler customJsonHandler = field.getAnnotation(CustomJsonHandler.class);
jsonHandlersByField.put(field, customJsonHandler);
}
return jsonHandlersByField.get(field);
}
private static JsonHandler instantiate(Class<? extends JsonHandler> cls) {
if (!jsonHandlers.containsKey(cls)) {
try {
jsonHandlers.put(cls, cls.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return jsonHandlers.get(cls);
}
}