JavaTypeSerializer.java
/*
** Module : JavaTypeSerializer.java
** Abstract : Base class to parse and serialize various Java types (including native types, POJO, record, etc).
**
** Copyright (c) 2022-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 CA 20220323 Created initial version.
** TJD 20220504 Java 11 compatibility minor changes
** 002 SBI 20221005 Fixed getSerializerInt for nested generic parameters. For example, ArrayList<Map<String, Object>>.
** TJD 20220504 Java 11 compatibility minor changes
** 003 DDF 20230620 Replaced static initialization of values from the directory configuration with
** a method called at server bootstrap.
** 004 TJD 20240123 Java 17 compatibility updates
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.rest.serializers;
import java.lang.reflect.*;
import java.math.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import org.reflections.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.node.*;
import com.goldencode.asm.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.Record;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.rest.*;
import com.goldencode.p2j.util.*;
/**
* The base class to be implemented by any custom or default serializer, to be used when invoking REST services
* which are performed as a direct Java call (on the appserver agent).
* <p>
* All public serializers defined in the <code>com.goldencode.p2j.rest.serializers</code> package are resolved
* and registered. Custom serializers can be specified by setting <code>rest/custom-java-serializers</code>
* to a Java package holding these custom serializers, in the application's jar.
* <p>
* All mutable instances created by the serializer's {@link #fromJson} method can be expected to have their
* changes reflected back on the caller, regardless if the argument is OUTPUT or not. It is the caller's
* responsibility to ensure that only OUTPUT, INPUT-OUTPUT or RETURN arguments are being processed.
*
* @param <T>
* The target type of this serializer. It can be a super-class or interface, a concrete type
* implementing a certain interface or class. When resolving the serializer, the one closest to
* the object's type will be resolved. For example, if the object's type is a DMO (like Book),
* and exists both a {@link RecordSerializer} and a custom serializer for this Book DMO, then the
* custom serializer will be resolved.
*/
@SuppressWarnings("unchecked")
public abstract class JavaTypeSerializer<T>
{
/** The serializer for {@link String} instances. */
protected static JavaTypeSerializer STRING_SERIALIZER;
/** The serializer for {@link Boolean} instances. */
protected static JavaTypeSerializer BOOLEAN_SERIALIZER;
/** The serializer for {@link BigDecimal} instances. */
protected static JavaTypeSerializer BIGDECIMAL_SERIALIZER;
/** The serializer for {@link BigInteger} instances. */
protected static JavaTypeSerializer BIGINTEGER_SERIALIZER;
/** The serializer for {@link Short} instances. */
protected static JavaTypeSerializer SHORT_SERIALIZER;
/** The serializer for {@link Integer} instances. */
protected static JavaTypeSerializer INTEGER_SERIALIZER;
/** The serializer for {@link Long} instances. */
protected static JavaTypeSerializer LONG_SERIALIZER;
/** The serializer for {@link Float} instances. */
protected static JavaTypeSerializer FLOAT_SERIALIZER;
/** The serializer for {@link Double} instances. */
protected static JavaTypeSerializer DOUBLE_SERIALIZER;
/** The serializer for Java byte arrays (byte[]). */
protected static JavaTypeSerializer BYTEARRAY_SERIALIZER;
/**
* The mapping of a type to its serializer. The type follows the convention of {@link Type#getTypeName()}.
*/
private static final Map<String, JavaTypeSerializer> SERIALIZERS = new ConcurrentHashMap<>();
/** The primitive type serializers. */
private static final Map<String, Class<?>> PRIMITIVE_TYPES;
static
{
Map<String, Class<?>> types = new HashMap<>();
types.put(boolean.class.getTypeName(), boolean.class);
types.put(byte.class.getTypeName(), byte.class);
types.put(char.class.getTypeName(), char.class);
types.put(short.class.getTypeName(), short.class);
types.put(int.class.getTypeName(), int.class);
types.put(long.class.getTypeName(), long.class);
types.put(float.class.getTypeName(), float.class);
types.put(double.class.getTypeName(), double.class);
PRIMITIVE_TYPES = Collections.unmodifiableMap(types);
}
/** Used to parse the JSON payload. */
protected final ThreadLocal<ObjectMapper> mapper = new ThreadLocal<ObjectMapper>()
{
protected ObjectMapper initialValue()
{
return new ObjectMapper();
};
};
/** The serializer's type. */
private final Class<T> type;
/**
* Initialize this serializer with the given type.
*
* @param type
* The target type.
*/
public JavaTypeSerializer(Class<T> type)
{
this.type = type;
}
/**
* Method called at server bootstrap that initializes values from the directory
* configuration. Until this method is called, default values are used.
*/
public static void bootstrap()
{
loadSerializers("com.goldencode.p2j.rest.serializers");
String customPackage = Utils.getDirectoryNodeString(null,
"rest/custom-java-serializers",
null,
false);
if (customPackage != null)
{
loadSerializers(customPackage);
}
STRING_SERIALIZER = getSerializer(String.class.getTypeName());
BOOLEAN_SERIALIZER = getSerializer(Boolean.class.getTypeName());
BIGDECIMAL_SERIALIZER = getSerializer(BigDecimal.class.getTypeName());
BIGINTEGER_SERIALIZER = getSerializer(BigInteger.class.getTypeName());
SHORT_SERIALIZER = getSerializer(Short.class.getTypeName());
INTEGER_SERIALIZER = getSerializer(Integer.class.getTypeName());
LONG_SERIALIZER = getSerializer(Long.class.getTypeName());
FLOAT_SERIALIZER = getSerializer(Float.class.getTypeName());
DOUBLE_SERIALIZER = getSerializer(Double.class.getTypeName());
BYTEARRAY_SERIALIZER = getSerializer(byte[].class.getTypeName());
}
/**
* Create an initial (default) instance for this serializer.
* <p>
* Can be null for non-mutable instances.
*
* @param definitionType
* The type as it appears at the parameter's definition.
*
* @return <code>null</code> for immutable types, a Java native value for native types or a new instance,
* for mutable types.
*
* @throws InstantiationException
* @throws IllegalAccessException
*/
public abstract T initialize(Class<? extends T> definitionType)
throws ReflectiveOperationException;
/**
* Parse the string JSON representation and assign it to the given argument. If null or not mutable, a
* new instance will be created.
*
* @param arg
* The argument (obtained via {@link #initialize}.
* @param sval
* The string JSON representation of this argument.
*
* @return The argument instance.
*
* @throws RequestArgumentError
* If the argument can't be parsed.
*/
public abstract T fromJson(T arg, String sval)
throws RequestArgumentError;
/**
* Serialize the given instance to JSON.
*
* @param val
* The instance to serialize.
*
* @return The JSON representation of this instance.
*/
public abstract JsonNode toJson(T val);
/**
* Given a {@link Type#getTypeName()} representation of a Java type, resolve it to a Java {@link Class}.
* <p>
* If this is a native type name, then the {@link #PRIMITIVE_TYPES} will resolve it.
* <p>
* If there is a serializer registered for this type in {@link #SERIALIZERS}, the the serializer's
* {@link #type} is returned.
* <p>
* Otherwise, it is assumed this is a Java type name, and will be resolved either by {@link Class#forName}
* or via the {@link AsmClassLoader}, in case of DMO types.
*
* @param stype
* The string representation of this type.
*
* @return See above.
*
* @throws ClassNotFoundException
* If the type can't be resolved.
*/
public static Class<?> resolveJavaType(String stype)
throws ClassNotFoundException
{
int idx = stype.indexOf('<');
String type = (idx < 0 ? stype : stype.substring(0, idx));
Class<?> res = PRIMITIVE_TYPES.get(type);
if (res != null)
{
return res;
}
JavaTypeSerializer serializer = SERIALIZERS.get(stype);
if (serializer != null)
{
return serializer.getType();
}
try
{
return Class.forName(type);
}
catch (ClassNotFoundException exc)
{
return AsmClassLoader.getInstance().findClass(type);
}
}
/**
* Get the serializer associated with the given type.
*
* @param stype
* The string representation of this type (including any generic parameters), as returned by
* {@link Type#getTypeName()}.
*
* @return See above.
*/
public static JavaTypeSerializer getSerializer(String stype)
{
return getSerializer(stype, SourceNameMapper.NO_EXTENT);
}
/**
* Get the serializer associated with the given type.
* <p>
* If the serializer is resolved to an {@link ArraySerializer}, a new instance is created and the specified
* extent is set.
*
* @param stype
* The string representation of this type (including any generic parameters), as returned by
* {@link Type#getTypeName()}.
* @param extent
* In case of arrays, a positive value indicating the array's length.
*
* @return See above.
*/
public static JavaTypeSerializer getSerializer(String stype, int extent)
{
JavaTypeSerializer serializer = getSerializerInt(stype, extent);
if (serializer instanceof ArraySerializer)
{
serializer = new ArraySerializer((ArraySerializer) serializer, extent);
}
return serializer;
}
/**
* Get the serializer associated with the given type.
*
* @param stype
* The string representation of this type (including any generic parameters), as returned by
* {@link Type#getTypeName()}.
* @param extent
* In case of arrays, a positive value indicating the array's length.
*
* @return See above.
*/
private static JavaTypeSerializer getSerializerInt(String stype, int extent)
{
JavaTypeSerializer serializer = SERIALIZERS.get(stype);
if (serializer != null)
{
return serializer;
}
if ("byte[]".equals(stype))
{
serializer = new ByteArraySerializer();
SERIALIZERS.put(stype, serializer);
return serializer;
}
int idx = stype.indexOf('<');
String type = (idx < 0 ? stype : stype.substring(0, idx));
serializer = SERIALIZERS.get(type);
if (serializer == null)
{
Class cls;
try
{
// check for arrays
if (type.indexOf('[') > 0)
{
int dim = 0;
String tarr = type;
while (tarr.indexOf('[') > 0)
{
dim = dim + 1;
tarr = tarr.substring(tarr.indexOf('[') + 1);
}
String arrType = type.substring(0, type.indexOf('['));
Class arrClazz = resolveJavaType(arrType);
int[] dimensions = new int[dim];
arrClazz = Array.newInstance(arrClazz, dimensions).getClass();
for (int i = 0; i < dim - 1; i++)
{
arrType += "[]";
}
JavaTypeSerializer ctype = getSerializer(arrType);
serializer = new ArraySerializer(arrClazz, ctype);
SERIALIZERS.put(stype, serializer);
return serializer;
}
cls = resolveJavaType(type);
}
catch (ClassNotFoundException e)
{
// should never happen, leave it for debugging purposes
throw new RuntimeException(e);
}
if (cls.isArray())
{
JavaTypeSerializer ctype = getSerializer(cls.getComponentType().getName());
serializer = new ArraySerializer(cls, ctype);
SERIALIZERS.put(stype, serializer);
return serializer;
}
// if not yet registered, look into all super-interfaces and all super-classes and find if
// anything is registered for it. this allows to have a e.g. java.util.Set serializer for any
// Set implementation.
Set<JavaTypeSerializer> found = new HashSet<>();
Utils.dfsClassHierarchy(cls, (scls) ->
{
JavaTypeSerializer ser = SERIALIZERS.get(scls.getName());
if (ser != null)
{
found.add(ser);
}
});
if (found.size() == 1)
{
serializer = found.iterator().next();
}
else
{
// look for the most specific serializer, which doesn't have a serializer for a super-class or
// implemented interface already registered
List<JavaTypeSerializer> candidates = new ArrayList<>();
for (JavaTypeSerializer ser1 : found)
{
boolean ok = true;
for (JavaTypeSerializer ser2 : found)
{
if (ser1 != ser2 && ser1.getType().isAssignableFrom(ser2.getType()))
{
ok = false;
break;
}
}
if (ok)
{
candidates.add(ser1);
}
}
if (candidates.size() != 1)
{
throw new IllegalStateException("Could not determine the serializer for " + cls.toString() +
" from: " + found.toString());
}
serializer = candidates.get(0);
}
}
if (serializer == null)
{
throw new NullPointerException("No serializer defined for " + type);
}
if (idx < 0)
{
// non-parameterized type
return serializer;
}
// create a new one with the parameters, and register it.
try
{
serializer = serializer.getClass().getDeclaredConstructor().newInstance();
}
catch (ReflectiveOperationException e)
{
// ignore, can't happen
}
List<JavaTypeSerializer> parameters = new ArrayList<>();
String sparams = stype.substring(idx + 1).trim();
sparams = sparams.substring(0, sparams.lastIndexOf('>')).trim();
// split by comma and get all the parameters
idx = sparams.indexOf(',');
int scan = 0;
int balance = 0;
while (idx > 0)
{
// parse generic parameter types
for(int i = scan; i < sparams.length() && i < idx; i++)
{
if (sparams.charAt(i) == '<')
{
balance++;
}
else if (sparams.charAt(i) == '>')
{
balance--;
}
}
if (balance == 0)
{
String ptype = sparams.substring(0, idx).trim();
parameters.add(getSerializer(ptype));
sparams = sparams.substring(idx + 1).trim();
scan = 0;
}
else
{
scan = idx + 1;
}
idx = sparams.indexOf(',', scan);
}
// add the last one
parameters.add(getSerializer(sparams));
JavaTypeSerializer[] params = parameters.toArray(new JavaTypeSerializer[parameters.size()]);
((ParameterizedTypeSerializer) serializer).setParameters(params);
SERIALIZERS.put(stype, serializer);
return serializer;
}
/**
* Get a serializer based on the JSON node's type.
* <p>
* If one can't be resolved, <code>null</code> is returned.
*
* @param node
* The JSON node.
*
* @return See above.
*/
private static JavaTypeSerializer getSerializer(JsonNode node)
{
if (node.isBigDecimal())
{
return BIGDECIMAL_SERIALIZER;
}
else if (node.isBigInteger())
{
return BIGINTEGER_SERIALIZER;
}
else if (node.isBinary())
{
return BYTEARRAY_SERIALIZER;
}
else if (node.isBoolean())
{
return BOOLEAN_SERIALIZER;
}
else if (node.isDouble())
{
return DOUBLE_SERIALIZER;
}
else if (node.isFloat())
{
return FLOAT_SERIALIZER;
}
else if (node.isFloatingPointNumber())
{
return FLOAT_SERIALIZER;
}
else if (node.isInt())
{
return INTEGER_SERIALIZER;
}
else if (node.isIntegralNumber())
{
return INTEGER_SERIALIZER;
}
else if (node.isLong())
{
return LONG_SERIALIZER;
}
else if (node.isNumber())
{
return DOUBLE_SERIALIZER;
}
else if (node.isShort())
{
return SHORT_SERIALIZER;
}
else if (node.isTextual())
{
return STRING_SERIALIZER;
}
return null;
}
/**
* Load all {@link JavaTypeSerializer} implementation in the given package.
* <p>
* Only non-abstract and public classes are resolved.
*
* @param pkg
* The package where the serializers reside.
*/
private static void loadSerializers(String pkg)
{
Reflections reflections = new Reflections(pkg);
Set<Class<? extends JavaTypeSerializer>> def = reflections.getSubTypesOf(JavaTypeSerializer.class);
for (Class<? extends JavaTypeSerializer> type : def)
{
if (Modifier.isAbstract(type.getModifiers()) || !Modifier.isPublic(type.getModifiers()))
{
continue;
}
try
{
JavaTypeSerializer ser;
try
{
ser = type.getConstructor().newInstance();
}
catch (IllegalArgumentException |
InvocationTargetException |
NoSuchMethodException |
SecurityException e)
{
throw new IllegalStateException("Serializer " + type.getName() +
" must have a public default constructor.", e);
}
JavaTypeSerializer old = SERIALIZERS.put(ser.getType().getTypeName(), ser);
if (old != null)
{
throw new IllegalStateException("More than one serializer registered for " + ser.getType());
}
}
catch (InstantiationException |
IllegalAccessException e)
{
throw new RuntimeException(e);
}
}
}
/**
* Get the serializer's type.
*
* @return See above.
*/
public Class<T> getType()
{
return type;
}
/**
* Parse the JSON node and assign it to the given argument. If null or not mutable, a new instance will be
* created.
*
* @param arg
* The argument (obtained via {@link #initialize}.
* @param node
* The JSON node for this argument.
*
* @return The argument instance.
*
* @throws RequestArgumentError
* If the argument can't be parsed.
*/
protected T fromJson(T arg, JsonNode node)
throws RequestArgumentError
{
return fromJson(arg, node.isTextual() ? node.asText() : node.toString());
}
/**
* Create a new {@link DataModelObject} of the given type.
*
* @param definitionType
* The DMO interface.
*
* @return A new {@link Record} instance.
*
* @throws InstantiationException
* @throws IllegalAccessException
*/
protected Record newRecord(Class<? extends DataModelObject> definitionType)
throws ReflectiveOperationException
{
DmoMeta dmoInfo = DmoMetadataManager.registerDmo((Class<? extends DataModelObject>) definitionType);
Record record = dmoInfo.getImplementationClass().getDeclaredConstructor().newInstance();
record.initialize(true);
return record;
}
/**
* Create a new instance of the given type. If the type is abstract, then the supplier will be used to
* create an instance.
*
* @param definitionType
* The parameter's definition type.
* @param def
* The supplier of a default instance of this type.
*
* @return See above.
*
* @throws InstantiationException
* @throws IllegalAccessException
*/
protected T newInstance(Class<? extends T> definitionType, Supplier<? extends T> def)
throws ReflectiveOperationException
{
if (Modifier.isAbstract(definitionType.getModifiers()))
{
return def.get();
}
return definitionType.getDeclaredConstructor().newInstance();
}
/**
* Read a JSON array and load it in a Java list.
*
* @param component
* The serializer for this array.
* @param ctype
* The component's type.
* @param sval
* The string JSON representation of this array.
*
* @return The array, read as a list.
*
* @throws RequestArgumentError
* If the array could not be parsed.
*/
protected List<Object> readArray(JavaTypeSerializer component, Class<?> ctype, String sval)
throws RequestArgumentError
{
List<Object> res = new ArrayList<>();
readArray(component, ctype, sval, res);
return res;
}
/**
* Given a JSON array represented as a string, load its values in a Java collection.
*
* @param component
* The serializer for the array's values. If <code>null</code>, it will be resolved from the
* value's type.
* @param sval
* The string representation of this JSON array.
* @param type
* The collection's concrete type.
* value's type.
*
* @return See above.
*
* @throws RequestArgumentError
* If the key or value can't be parsed.
*/
protected Collection readArray(JavaTypeSerializer component, String sval, Class<? extends Collection> type)
throws RequestArgumentError
{
try
{
Collection res = type.getDeclaredConstructor().newInstance();
Class<?> ctype = component == null ? null : component.getType();
readArray(component, ctype, sval, res);
return res;
}
catch (ReflectiveOperationException e)
{
throw new RequestArgumentError("Could not parse " + sval, e);
}
}
/**
* Given a JSON array represented as a string, load its values in a Java collection.
*
* @param component
* The serializer for the array's values. If <code>null</code>, it will be resolved from the
* value's type.
* @param ctype
* The array's component concrete type. If <code>null</code> it will be resolved from the
* value's type.
* @param sval
* The string representation of this JSON array.
* @param target
* The target collection where to load the array.
*
* @throws RequestArgumentError
* If the key or value can't be parsed.
*/
protected void readArray(JavaTypeSerializer component, Class<?> ctype, String sval, Collection target)
throws RequestArgumentError
{
try
{
ArrayNode arr = (ArrayNode) mapper.get().readTree(sval);
for (int i = 0; i < arr.size(); i++)
{
JsonNode node = arr.get(i);
Object val;
JavaTypeSerializer serializer = component == null ? getSerializer(node) : component;
if (serializer != null)
{
val = serializer.initialize(ctype == null ? serializer.getType() : ctype);
val = serializer.fromJson(val, node);
}
else
{
// if the component type is not known, always assume String
val = node.isNull() ? null : node.isTextual() ? node.asText() : node.toString();
}
target.add(val);
}
}
catch (JsonProcessingException |
ReflectiveOperationException e)
{
throw new RequestArgumentError("Could not parse " + sval, e);
}
}
/**
* Given a JSON object represented as a string, load its keys and values in a map.
*
* @param pkey
* The serializer for the keys. If <code>null</code>, it will be resolved from the keys's type.
* @param pval
* The serializer for the values. If <code>null</code>, it will be resolved from the value's type.
* @param sval
* The string representation of this JSON object.
* @param type
* The map's concrete type.
*
* @return See above.
*
* @throws RequestArgumentError
* If the key or value can't be parsed.
*/
protected Map readMap(JavaTypeSerializer pkey,
JavaTypeSerializer pval,
String sval,
Class<? extends Map> type)
throws RequestArgumentError
{
try
{
Map res = type.getDeclaredConstructor().newInstance();
ObjectNode obj = (ObjectNode) mapper.get().readTree(sval);
Iterator<Map.Entry<String, JsonNode>> iter = obj.fields();
while (iter.hasNext())
{
Map.Entry<String, JsonNode> field = iter.next();
String key = field.getKey();
JsonNode node = field.getValue();
JavaTypeSerializer serializer = pval == null ? getSerializer(node) : pval;
Object val;
if (serializer != null)
{
val = serializer.initialize(serializer.getType());
val = serializer.fromJson(val, node);
}
else
{
// if the component type is not known, always assume String
val = node.isNull() ? null : node.isTextual() ? node.asText() : node.toString();
}
Object okey = key;
if (pkey != null)
{
// serialize on the expected type
okey = pkey.initialize(pkey.getType());
okey = pkey.fromJson(okey, key);
}
res.put(okey, val);
}
return res;
}
catch (JsonProcessingException |
ReflectiveOperationException e)
{
throw new RequestArgumentError("Could not parse " + sval, e);
}
}
/**
* Create a JSON representation as an array, for the given collection.
*
* @param c
* The collection.
* @param component
* The component's type. If <code>null</code>, it will be resolved from the value's type.
*
* @return See above.
*/
protected JsonNode toJsonArray(Collection c, JavaTypeSerializer component)
{
if (c == null)
{
return JsonNodeFactory.instance.nullNode();
}
int size = c.size();
ArrayNode arr = JsonNodeFactory.instance.arrayNode(size);
Iterator iter = c.iterator();
while (iter.hasNext())
{
Object val = iter.next();
JavaTypeSerializer ser = component;
if (ser == null)
{
if (val == null)
{
arr.add(JsonNodeFactory.instance.nullNode());
continue;
}
ser = getSerializer(val.getClass().getTypeName());
}
JsonNode el = ser.toJson(val);
arr.add(el);
}
return arr;
}
/**
* Create a JSON representation of the specified map.
*
* @param map
* The map.
* @param pkey
* The key's serializer. This is not used, the keys are assumed as string.
* @param pval
* The value's serializer. If <code>null</code>, it will be resolved from the value's type.
*
* @return See above.
*/
protected JsonNode toJsonObject(Map map,
JavaTypeSerializer pkey,
JavaTypeSerializer pval)
{
ObjectNode res = JsonNodeFactory.instance.objectNode();
Iterator<Map.Entry> iter = map.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry entry = iter.next();
String key = entry.getKey().toString(); // the keys are always by their String representation
JavaTypeSerializer ser = pval;
Object val = entry.getValue();
if (ser == null)
{
if (val == null)
{
res.set(key, JsonNodeFactory.instance.nullNode());
continue;
}
ser = getSerializer(val.getClass().getTypeName());
}
JsonNode node = ser.toJson(entry.getValue());
res.set(key, node);
}
return res;
}
}