org.fujion.common.JSONUtil Maven / Gradle / Ivy
/*
* #%L
* fujion
* %%
* Copyright (C) 2018 Fujion Framework
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* #L%
*/
package org.fujion.common;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeSerializer;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* A set of static methods supporting serialization and deserialization of objects using the JSON
* format. This particular implementation uses the Jackson JSON library, but other JSON
* implementations could be used.
*/
public class JSONUtil {
private static final String DEFAULT_TYPE_PROPERTY = "@class";
private static final Map mappers = new ConcurrentHashMap<>();
/**
* Identifies properties that require type metadata (via property specified in typeProperty).
*/
private static class CWTypeResolverBuilder extends StdTypeResolverBuilder {
@Override
public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType,
Collection subtypes) {
return noTypeInfo(baseType) ? null
: new AsPropertyTypeDeserializer(baseType, _customIdResolver, _typeProperty, _typeIdVisible, baseType);
}
@Override
public TypeSerializer buildTypeSerializer(SerializationConfig config, JavaType baseType,
Collection subtypes) {
return noTypeInfo(baseType) ? null : new AsPropertyTypeSerializerEx(_customIdResolver, null, _typeProperty);
}
/**
* Returns true if no type info should be written for this base type.
*
* @param baseType Base type.
* @return True to suppress writing of type information.
*/
private boolean noTypeInfo(JavaType baseType) {
return baseType.isPrimitive() || baseType.isArrayType() || baseType.isCollectionLikeType()
|| Date.class.isAssignableFrom(baseType.getRawClass());
}
}
/**
* Resolves type identifiers to classes. Supports aliases and class names.
*/
private static class CWTypedIdResolver implements TypeIdResolver {
private JavaType baseType;
protected CWTypedIdResolver() {
}
@Override
public void init(JavaType baseType) {
this.baseType = baseType;
}
@Override
public String idFromValue(Object value) {
return findId(value.getClass());
}
@Override
public String idFromValueAndType(Object value, Class> suggestedType) {
return findId(suggestedType);
}
@Override
public String idFromBaseType() {
return findId(baseType.getRawClass());
}
@Override
public JavaType typeFromId(DatabindContext context, String id) {
return typeFromId(context.getTypeFactory(), id);
}
/**
* Returns a type token given its alias or class name.
*
* @param typeFactory The type factory.
* @param id Alias or class name.
* @return A type token.
*/
private JavaType typeFromId(TypeFactory typeFactory, String id) {
try {
Class> clazz = aliasToClass.get(id);
clazz = clazz == null ? typeFactory.findClass(id) : clazz;
return typeFactory.constructType(clazz);
} catch (ClassNotFoundException e) {
throw MiscUtil.toUnchecked(e);
}
}
@Override
public Id getMechanism() {
return Id.CUSTOM;
}
@Override
public String getDescForKnownTypeIds() {
return "CWTypeResolverBuilder";
}
}
/**
* Required to suppress writing of type information except for top-level objects.
*/
public final static class AsPropertyTypeSerializerEx extends AsPropertyTypeSerializer {
public AsPropertyTypeSerializerEx(TypeIdResolver idRes, BeanProperty property, String propName) {
super(idRes, property, propName);
}
@Override
public void writeTypePrefixForObject(Object value, JsonGenerator jgen) throws IOException, JsonProcessingException {
boolean needTypeId = jgen.getOutputContext().inRoot() || jgen.getOutputContext().inArray();
jgen.writeStartObject();
if (needTypeId) {
jgen.writeStringField(_typePropertyName, idFromValue(value));
}
}
}
private static final Map> aliasToClass = new HashMap<>();
private static final Map, String> classToAlias = new HashMap<>();
/**
* Returns an instance of the mapper for the default type property.
*
* @return A mapper.
*/
public static ObjectMapper getMapper() {
return getMapper(null);
}
/**
* Returns an instance of the mapper for the specified type property.
*
* @param typeProperty The name of the property signifying the data type.
* @return A mapper.
*/
public static ObjectMapper getMapper(String typeProperty) {
typeProperty = typeProperty == null ? DEFAULT_TYPE_PROPERTY : typeProperty;
ObjectMapper mapper = mappers.get(typeProperty);
return mapper == null ? initMapper(typeProperty) : mapper;
}
/**
* Initializes a mapper in a thread-safe way.
*
* @param typeProperty The name of the property specifying the object type.
* @return The initialized mapper.
*/
private static ObjectMapper initMapper(String typeProperty) {
synchronized (mappers) {
ObjectMapper mapper = mappers.get(typeProperty);
if (mapper == null) {
mapper = new ObjectMapper();
TypeResolverBuilder> typer = new CWTypeResolverBuilder();
typer = typer.init(JsonTypeInfo.Id.CUSTOM, new CWTypedIdResolver());
typer = typer.inclusion(JsonTypeInfo.As.PROPERTY);
typer = typer.typeProperty(typeProperty);
mapper.setDefaultTyping(typer);
mappers.put(typeProperty, mapper);
}
return mapper;
}
}
/**
* Register an alias for the specified class. The alias will be used when serializing objects of
* this class. A given class or alias may only be registered once.
*
* @param alias Alias to be used for serialization.
* @param clazz Class to be associated with the alias.
*/
public static synchronized void registerAlias(String alias, Class> clazz) {
if (aliasToClass.containsKey(alias) && aliasToClass.get(alias) != clazz) {
throw new RuntimeException("Alias '" + alias + "' is already registered to another class.");
}
if (classToAlias.containsKey(clazz) && classToAlias.get(clazz).equals(alias)) {
throw new RuntimeException("Class '" + clazz.getName() + "' is already registered to another alias.");
}
aliasToClass.put(alias, clazz);
classToAlias.put(clazz, alias);
}
/**
* Removes a registered alias.
*
* @param name Alias to be unregistered.
*/
public static synchronized void unregisterAlias(String name) {
classToAlias.remove(aliasToClass.get(name));
aliasToClass.remove(name);
}
/**
* Returns an alias given its associated class.
*
* @param clazz The class whose alias is sought.
* @return The alias associated with the specified class, or null if one does not exist.
*/
public static final String getAlias(Class> clazz) {
return classToAlias.get(clazz);
}
/**
* Returns the alias for a class or its class name if an alias has not been registered. This
* value is used to identify the class type when serializing.
*
* @param clazz Class whose id is sought.
* @return The identifier to be used for serialization.
*/
private static String findId(Class> clazz) {
String id = classToAlias.get(clazz);
return id == null ? clazz.getName() : id;
}
/**
* Sets the date format to be used when serializing dates.
*
* @param dateFormat Date format to use.
*/
public static void setDateFormat(DateFormat dateFormat) {
setDateFormat(null, dateFormat);
}
/**
* Sets the date format to be used when serializing dates.
*
* @param typeProperty The name of the property signifying the data type.
* @param dateFormat Date format to use.
*/
public static void setDateFormat(String typeProperty, DateFormat dateFormat) {
getMapper(typeProperty).setDateFormat(dateFormat);
}
/**
* Serializes an object to JSON format.
*
* @param object Object to be serialized.
* @return Serialized form of the object in JSON format.
*/
public static String serialize(Object object) {
return serialize(object, false);
}
/**
* Serializes an object to JSON format.
*
* @param object Object to be serialized.
* @param prettyPrint If true, format output for display.
* @return Serialized form of the object in JSON format.
*/
public static String serialize(Object object, boolean prettyPrint) {
return serialize(null, object, prettyPrint);
}
/**
* Serializes an object to JSON format.
*
* @param typeProperty The name of the property signifying the data type.
* @param object Object to be serialized.
* @return Serialized form of the object in JSON format.
*/
public static String serialize(String typeProperty, Object object) {
return serialize(typeProperty, object, false);
}
/**
* Serializes an object to JSON format.
*
* @param typeProperty The name of the property signifying the data type.
* @param object Object to be serialized.
* @param prettyPrint If true, format output for display.
* @return Serialized form of the object in JSON format.
*/
public static String serialize(String typeProperty, Object object, boolean prettyPrint) {
try {
ObjectMapper mapper = getMapper(typeProperty);
return prettyPrint ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(object)
: mapper.writeValueAsString(object);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Deserializes an object from JSON format.
*
* @param data Serialized form of the object.
* @return An instance of the deserialized object.
*/
public static Object deserialize(String data) {
return deserialize(null, data);
}
/**
* Deserializes an object from JSON format.
*
* @param typeProperty The name of the property signifying the data type.
* @param data Serialized form of the object.
* @return An instance of the deserialized object.
*/
public static Object deserialize(String typeProperty, String data) {
if (data == null) {
return null;
}
if (data.startsWith("[")) {
return deserializeList(typeProperty, data, Object.class);
}
try {
return getMapper(typeProperty).readValue(data, Object.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Deserializes a list of objects.
*
* @param The list elements' class.
* @param data Serialized form of the list in JSON format.
* @param clazz The class of objects found in the list.
* @return A list of objects of the specified type.
*/
public static List deserializeList(String data, Class clazz) {
return deserializeList(null, data, clazz);
}
/**
* Deserializes a list of objects.
*
* @param The list elements' class.
* @param typeProperty The name of the property signifying the data type.
* @param data Serialized form of the list in JSON format.
* @param clazz The class of objects found in the list.
* @return A list of objects of the specified type.
*/
public static List deserializeList(String typeProperty, String data, Class clazz) {
try {
return getMapper(typeProperty).readValue(data, new TypeReference>() {});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Merges one JSON tree (srcNode) into another (destNode).
*
* @param destNode The tree receiving the merged node.
* @param srcNode The tree supplying the nodes to merge.
* @return The destination node post merging.
*/
public static JsonNode merge(JsonNode destNode, JsonNode srcNode) {
return merge(destNode, srcNode, false);
}
/**
* Merges one JSON tree (srcNode) into another (destNode).
*
* @param destNode The tree receiving the merged node.
* @param srcNode The tree supplying the nodes to merge.
* @param deleteOnNull If true and a null value is encountered in the source, delete the
* corresponding node in the destination. If false, null values are treated like any
* other value.
* @return The destination node post merging.
*/
public static JsonNode merge(JsonNode destNode, JsonNode srcNode, boolean deleteOnNull) {
Iterator fieldNames = srcNode.fieldNames();
while (fieldNames.hasNext()) {
String fieldName = fieldNames.next();
JsonNode jsonNode = destNode.get(fieldName);
// if field exists and is an embedded object
if (jsonNode != null && jsonNode.isObject()) {
merge(jsonNode, srcNode.get(fieldName), deleteOnNull);
} else if (destNode instanceof ObjectNode) {
// Overwrite field
JsonNode value = srcNode.get(fieldName);
if (deleteOnNull && value.isNull()) {
((ObjectNode) destNode).remove(fieldName);
} else {
((ObjectNode) destNode).set(fieldName, value);
}
}
}
return destNode;
}
/**
* Enforce static class.
*/
private JSONUtil() {
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy