SerializationContextExt.java
package tools.jackson.databind.ser;
import java.util.*;
import com.fasterxml.jackson.annotation.ObjectIdGenerator;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.TokenStreamFactory;
import tools.jackson.databind.*;
import tools.jackson.databind.cfg.GeneratorSettings;
import tools.jackson.databind.cfg.HandlerInstantiator;
import tools.jackson.databind.introspect.Annotated;
import tools.jackson.databind.introspect.BeanPropertyDefinition;
import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.node.JsonNodeFactory;
import tools.jackson.databind.node.TreeBuildingGenerator;
import tools.jackson.databind.util.ClassUtil;
import tools.jackson.databind.util.TokenBuffer;
/**
* Extension over {@link SerializationContext} that adds methods needed by
* {@link ObjectMapper} (and {@link ObjectWriter}) but that are not to be exposed
* as general context during serialization.
*<p>
* Also note that all custom {@link SerializationContext}
* implementations must sub-class this class: {@link ObjectMapper}
* requires this type, not basic provider type.
*/
public class SerializationContextExt
extends SerializationContext
{
/*
/**********************************************************************
/* Additional state
/**********************************************************************
*/
/**
* Per-serialization map Object Ids that have seen so far, iff
* Object Id handling is enabled.
*/
protected transient Map<Object, WritableObjectId> _seenObjectIds;
protected transient ArrayList<ObjectIdGenerator<?>> _objectIdGenerators;
/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/
protected SerializationContextExt(TokenStreamFactory streamFactory,
SerializationConfig config, GeneratorSettings genSettings,
SerializerFactory f, SerializerCache cache) {
super(streamFactory, config, genSettings, f, cache);
}
/*
/**********************************************************************
/* Abstract method impls, factory methods
/**********************************************************************
*/
@Override
public ValueSerializer<Object> serializerInstance(Annotated annotated, Object serDef)
{
if (serDef == null) {
return null;
}
ValueSerializer<?> ser;
if (serDef instanceof ValueSerializer serializer) {
ser = serializer;
} else {
// Alas, there's no way to force return type of "either class
// X or Y" -- need to throw an exception after the fact
if (!(serDef instanceof Class)) {
reportBadDefinition(annotated.getType(),
"AnnotationIntrospector returned serializer definition of type "
+serDef.getClass().getName()+"; expected type `ValueSerializer` or `Class<ValueSerializer>` instead");
}
Class<?> serClass = (Class<?>)serDef;
// there are some known "no class" markers to consider too:
if (serClass == ValueSerializer.None.class || ClassUtil.isBogusClass(serClass)) {
return null;
}
if (!ValueSerializer.class.isAssignableFrom(serClass)) {
reportBadDefinition(annotated.getType(),
"AnnotationIntrospector returned Class `"
+serClass.getName()+"`; expected `Class<ValueSerializer>`");
}
HandlerInstantiator hi = _config.getHandlerInstantiator();
ser = (hi == null) ? null : hi.serializerInstance(_config, annotated, serClass);
if (ser == null) {
ser = (ValueSerializer<?>) ClassUtil.createInstance(serClass,
_config.canOverrideAccessModifiers());
}
}
return (ValueSerializer<Object>) _handleResolvable(ser);
}
@Override
public Object includeFilterInstance(BeanPropertyDefinition forProperty,
Class<?> filterClass)
{
if (filterClass == null) {
return null;
}
HandlerInstantiator hi = _config.getHandlerInstantiator();
Object filter = (hi == null) ? null : hi.includeFilterInstance(_config, forProperty, filterClass);
if (filter == null) {
filter = ClassUtil.createInstance(filterClass,
_config.canOverrideAccessModifiers());
}
return filter;
}
@Override
public boolean includeFilterSuppressNulls(Object filter)
{
if (filter == null) {
return true;
}
// should let filter decide what to do with nulls:
// But just in case, let's handle unexpected (from our perspective) problems explicitly
try {
return filter.equals(null);
} catch (Exception e) {
String msg = String.format(
"Problem determining whether filter of type '%s' should filter out `null` values: (%s) %s",
filter.getClass().getName(), e.getClass().getName(), ClassUtil.exceptionMessage(e));
reportBadDefinition(filter.getClass(), msg, e);
return false; // never gets here
}
}
/*
/**********************************************************************
/* Abstract method impls, serialization-like methods
/**********************************************************************
*/
/**
* Method that
* will convert given Java value (usually bean) into its
* equivalent Tree model {@link JsonNode} representation.
* Functionally similar to serializing value into token stream and parsing that
* stream back as tree model node,
* but more efficient as {@link TokenBuffer} is used to contain the intermediate
* representation instead of fully serialized contents.
*<p>
* NOTE: while results are usually identical to that of serialization followed
* by deserialization, this is not always the case. In some cases serialization
* into intermediate representation will retain encapsulation of things like
* raw value ({@link tools.jackson.databind.util.RawValue}) or basic
* node identity ({@link JsonNode}). If so, result is a valid tree, but values
* are not re-constructed through actual format representation. So if transformation
* requires actual materialization of encoded content,
* it will be necessary to do actual serialization.
*
* @param <T> Actual node type; usually either basic {@link JsonNode} or
* {@link tools.jackson.databind.node.ObjectNode}
* @param fromValue Java value to convert
*
* @return (non-null) Root node of the resulting content tree: in case of
* {@code null} value node for which {@link JsonNode#isNull()} returns {@code true}.
*
* @since 3.0
*/
@Override
@SuppressWarnings({ "unchecked" })
public <T extends JsonNode> T valueToTree(Object fromValue)
throws JacksonException
{
final JsonNodeFactory nodeF = _config.getNodeFactory();
if (fromValue == null) {
return (T) nodeF.nullNode();
}
try (TreeBuildingGenerator gen = TreeBuildingGenerator.forSerialization(this, nodeF)) {
// 16-Jul-2025, tatu: [databind#5225] Must assign generator
_assignGenerator(gen);
final Class<?> rawType = fromValue.getClass();
final ValueSerializer<Object> ser = findTypedValueSerializer(rawType, true);
// 25-Jul-2023, tatu: Copied from other places; as per [databind#4047]
// need to add root-wrapping
PropertyName rootName = _config.getFullRootName();
if (rootName == null) { // not explicitly specified
if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
_serialize(gen, fromValue, ser, findRootName(rawType));
} else {
_serialize(gen, fromValue, ser);
}
} else if (!rootName.isEmpty()) {
_serialize(gen, fromValue, ser, rootName);
} else {
_serialize(gen, fromValue, ser);
}
return (T) gen.treeBuilt();
}
}
/*
/**********************************************************************
/* Object Id handling
/**********************************************************************
*/
@Override
public WritableObjectId findObjectId(Object forPojo, ObjectIdGenerator<?> generatorType)
{
if (_seenObjectIds == null) {
_seenObjectIds = _createObjectIdMap();
} else {
WritableObjectId oid = _seenObjectIds.get(forPojo);
if (oid != null) {
return oid;
}
}
// Not seen yet; must add an entry, return it. For that, we need generator
ObjectIdGenerator<?> generator = null;
if (_objectIdGenerators == null) {
_objectIdGenerators = new ArrayList<>(8);
} else {
for (int i = 0, len = _objectIdGenerators.size(); i < len; ++i) {
ObjectIdGenerator<?> gen = _objectIdGenerators.get(i);
if (gen.canUseFor(generatorType)) {
generator = gen;
break;
}
}
}
if (generator == null) {
generator = generatorType.newForSerialization(this);
_objectIdGenerators.add(generator);
}
WritableObjectId oid = new WritableObjectId(generator);
_seenObjectIds.put(forPojo, oid);
return oid;
}
/**
* Overridable helper method used for creating {@link java.util.Map}
* used for storing mappings from serializable objects to their
* Object Ids.
*/
protected Map<Object,WritableObjectId> _createObjectIdMap()
{
// 06-Aug-2013, tatu: We may actually want to use equality,
// instead of identity... so:
if (isEnabled(SerializationFeature.USE_EQUALITY_FOR_OBJECT_ID)) {
return new HashMap<>();
}
return new IdentityHashMap<>();
}
/*
/**********************************************************************
/* Extended API: simple accessors
/**********************************************************************
*/
/**
* Accessor for the {@link JsonGenerator} currently in use for serializing
* content. Null for blueprint instances; non-null for actual active
* provider instances.
*/
@Override
public JsonGenerator getGenerator() {
return _generator;
}
/*
/**********************************************************************
/* Extended API called by ObjectMapper: value serialization
/**********************************************************************
*/
/**
* The method to be called by {@link ObjectMapper} and {@link ObjectWriter}
* for serializing given value, using serializers that
* this provider has access to (via caching and/or creating new serializers
* as need be).
*/
public void serializeValue(JsonGenerator gen, Object value) throws JacksonException
{
_assignGenerator(gen);
if (value == null) {
_serializeNull(gen);
return;
}
final Class<?> cls = value.getClass();
// true, since we do want to cache root-level typed serializers (ditto for null property)
final ValueSerializer<Object> ser = findTypedValueSerializer(cls, true);
PropertyName rootName = _config.getFullRootName();
if (rootName == null) { // not explicitly specified
if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
_serialize(gen, value, ser, findRootName(cls));
return;
}
} else if (!rootName.isEmpty()) {
_serialize(gen, value, ser, rootName);
return;
}
_serialize(gen, value, ser);
}
/**
* The method to be called by {@link ObjectMapper} and {@link ObjectWriter}
* for serializing given value (assumed to be of specified root type,
* instead of runtime type of value),
* using serializers that
* this provider has access to (via caching and/or creating new serializers
* as need be),
*
* @param rootType Type to use for locating serializer to use, instead of actual
* runtime type. Must be actual type, or one of its super types
*/
public void serializeValue(JsonGenerator gen, Object value, JavaType rootType) throws JacksonException
{
_assignGenerator(gen);
if (value == null) {
_serializeNull(gen);
return;
}
// Let's ensure types are compatible at this point
if (!rootType.getRawClass().isAssignableFrom(value.getClass())) {
_reportIncompatibleRootType(value, rootType);
}
// root value, not reached via property:
ValueSerializer<Object> ser = findTypedValueSerializer(rootType, true);
PropertyName rootName = _config.getFullRootName();
if (rootName == null) { // not explicitly specified
if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
_serialize(gen, value, ser, findRootName(rootType));
return;
}
} else if (!rootName.isEmpty()) {
_serialize(gen, value, ser, rootName);
return;
}
_serialize(gen, value, ser);
}
/**
* The method to be called by {@link ObjectWriter}
* for serializing given value (assumed to be of specified root type,
* instead of runtime type of value), when it may know specific
* {@link ValueSerializer} to use.
*
* @param rootType Type to use for locating serializer to use, instead of actual
* runtime type, if no serializer is passed
* @param ser Root Serializer to use, if not null
*/
public void serializeValue(JsonGenerator gen, Object value, JavaType rootType,
ValueSerializer<Object> ser) throws JacksonException
{
_assignGenerator(gen);
if (value == null) {
_serializeNull(gen);
return;
}
// Let's ensure types are compatible at this point
if ((rootType != null) && !rootType.getRawClass().isAssignableFrom(value.getClass())) {
_reportIncompatibleRootType(value, rootType);
}
// root value, not reached via property:
if (ser == null) {
ser = findTypedValueSerializer(rootType, true);
}
PropertyName rootName = _config.getFullRootName();
if (rootName == null) { // not explicitly specified
if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
rootName = (rootType == null)
? findRootName(value.getClass())
: findRootName(rootType);
_serialize(gen, value, ser, rootName);
return;
}
} else if (!rootName.isEmpty()) {
_serialize(gen, value, ser, rootName);
return;
}
_serialize(gen, value, ser);
}
/**
* Alternate serialization call used for polymorphic types, when {@link TypeSerializer}
* is already known, but the actual serializer may or may not be.
*/
public void serializePolymorphic(JsonGenerator gen, Object value, JavaType rootType,
ValueSerializer<Object> valueSer, TypeSerializer typeSer)
throws JacksonException
{
_assignGenerator(gen);
if (value == null) {
_serializeNull(gen);
return;
}
// Let's ensure types are compatible at this point
if ((rootType != null) && !rootType.getRawClass().isAssignableFrom(value.getClass())) {
_reportIncompatibleRootType(value, rootType);
}
/* 12-Jun-2015, tatu: nominal root type is necessary for Maps at least;
* possibly collections, but can cause problems for other polymorphic
* types. We really need to distinguish between serialization type,
* base type; but right we don't. Hence this check
*/
if (valueSer == null) {
if ((rootType != null) && rootType.isContainerType()) {
valueSer = handleRootContextualization(findValueSerializer(rootType));
} else {
valueSer = handleRootContextualization(findValueSerializer(value.getClass()));
}
}
final boolean wrap;
PropertyName rootName = _config.getFullRootName();
if (rootName == null) {
wrap = _config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE);
if (wrap) {
gen.writeStartObject();
PropertyName pname = findRootName(value.getClass());
gen.writeName(pname.simpleAsEncoded(_config));
}
} else if (rootName.isEmpty()) {
wrap = false;
} else {
wrap = true;
gen.writeStartObject();
gen.writeName(rootName.getSimpleName());
}
valueSer.serializeWithType(value, gen, this, typeSer);
if (wrap) {
gen.writeEndObject();
}
}
private final void _serialize(JsonGenerator gen, Object value,
ValueSerializer<Object> ser, PropertyName rootName)
throws JacksonException
{
gen.writeStartObject();
gen.writeName(rootName.simpleAsEncoded(_config));
ser.serialize(value, gen, this);
gen.writeEndObject();
}
private final void _serialize(JsonGenerator gen, Object value,
ValueSerializer<Object> ser)
throws JacksonException
{
ser.serialize(value, gen, this);
}
/**
* Helper method called when root value to serialize is null
*/
protected void _serializeNull(JsonGenerator gen) throws JacksonException
{
ValueSerializer<Object> ser = getDefaultNullValueSerializer();
ser.serialize(null, gen, this);
}
/*
/**********************************************************************
/* Extended API called by ObjectMapper: other
/**********************************************************************
*/
/**
* The method to be called by {@link ObjectMapper} and {@link ObjectWriter}
* to expose the format of the given type to the given visitor
*
* @param javaType The type for which to generate format
* @param visitor the visitor to accept the format
*/
public void acceptJsonFormatVisitor(JavaType javaType, JsonFormatVisitorWrapper visitor)
{
Objects.requireNonNull(javaType);
// no need for embedded type information for JSON schema generation (all
// type information it needs is accessible via "untyped" serializer)
visitor.setContext(this);
findRootValueSerializer(javaType).acceptJsonFormatVisitor(visitor, javaType);
}
/*
/**********************************************************************
/* Helper classes
/**********************************************************************
*/
/**
* Concrete implementation defined separately so it can be declared `final`.
* Alternate implements should instead just extend {@link SerializationContextExt}
*/
public final static class Impl
extends SerializationContextExt
{
public Impl(TokenStreamFactory streamFactory,
SerializationConfig config, GeneratorSettings genSettings,
SerializerFactory f, SerializerCache cache) {
super(streamFactory, config, genSettings, f, cache);
}
}
}