BeanAsArraySerializer.java
package tools.jackson.databind.ser.bean;
import java.util.Set;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.JsonToken;
import tools.jackson.core.type.WritableTypeId;
import tools.jackson.databind.*;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.ser.BeanPropertyWriter;
import tools.jackson.databind.ser.impl.ObjectIdWriter;
import tools.jackson.databind.util.NameTransformer;
/**
* Specialized POJO serializer that differs from
* {@link tools.jackson.databind.ser.BeanSerializer}
* in that instead of producing a JSON Object it will output
* a JSON Array, omitting field names, and serializing values in
* specified serialization order.
* This behavior is usually triggered by using annotation
* {@link com.fasterxml.jackson.annotation.JsonFormat} or its
* equivalents.
*<p>
* This serializer can be used for "simple" instances; and will NOT
* be used if one of following is true:
*<ul>
* <li>Unwrapping is used (no way to expand out array in JSON Object)
* </li>
* <li>Type information ("type id") is to be used: while this could work
* for some embedding methods, it would likely cause conflicts.
* </li>
* <li>Object Identity ("object id") is used: while references would work,
* the problem is inclusion of id itself.
* </li>
*</ul>
* Note that it is theoretically possible that last 2 issues could be addressed
* (by reserving room in array, for example); and if so, support improved.
*<p>
* In cases where array-based output is not feasible, this serializer
* can instead delegate to the original Object-based serializer; this
* is why a reference is retained to the original serializer.
*/
public class BeanAsArraySerializer
extends BeanSerializerBase
{
/**
* Serializer that would produce JSON Object version; used in
* cases where array output cannot be used.
*/
protected final BeanSerializerBase _defaultSerializer;
/*
/**********************************************************************
/* Life-cycle: constructors
/**********************************************************************
*/
public BeanAsArraySerializer(BeanSerializerBase src) {
super(src, (ObjectIdWriter) null);
_defaultSerializer = src;
}
protected BeanAsArraySerializer(BeanSerializerBase src, Set<String> toIgnore, Set<String> toInclude) {
super(src, toIgnore, toInclude);
_defaultSerializer = src;
}
protected BeanAsArraySerializer(BeanSerializerBase src,
ObjectIdWriter oiw, Object filterId) {
super(src, oiw, filterId);
_defaultSerializer = src;
}
/*
/**********************************************************************
/* Life-cycle: factory methods, fluent factories
/**********************************************************************
*/
/**
* @since 3.0
*/
public static BeanSerializerBase construct(BeanSerializerBase src)
{
BeanSerializerBase ser = UnrolledBeanAsArraySerializer.tryConstruct(src);
if (ser != null) {
return ser;
}
return new BeanAsArraySerializer(src);
}
@Override
public ValueSerializer<Object> unwrappingSerializer(NameTransformer transformer) {
// If this gets called, we will just need delegate to the default
// serializer, to "undo" as-array serialization
return _defaultSerializer.unwrappingSerializer(transformer);
}
@Override
public boolean isUnwrappingSerializer() {
return false;
}
@Override
public BeanSerializerBase withObjectIdWriter(ObjectIdWriter objectIdWriter) {
// can't handle Object Ids, for now, so:
return _defaultSerializer.withObjectIdWriter(objectIdWriter);
}
@Override
public BeanSerializerBase withFilterId(Object filterId) {
return new BeanAsArraySerializer(this, _objectIdWriter, filterId);
}
@Override
protected BeanAsArraySerializer withByNameInclusion(Set<String> toIgnore, Set<String> toInclude) {
return new BeanAsArraySerializer(this, toIgnore, toInclude);
}
@Override // @since 2.11.1
protected BeanSerializerBase withProperties(BeanPropertyWriter[] properties,
BeanPropertyWriter[] filteredProperties) {
// 16-Jun-2020, tatu: Added for [databind#2759] but with as-array we
// probably do not want to reorder anything; so actually leave unchanged
return this;
}
@Override
protected BeanSerializerBase asArraySerializer() {
// already is one, so:
return this;
}
/*
/**********************************************************************
/* ValueSerializer implementation that differs between impls
/**********************************************************************
*/
// Re-defined from base class, due to differing prefixes
@Override
public void serializeWithType(Object bean, JsonGenerator gen,
SerializationContext ctxt, TypeSerializer typeSer)
throws JacksonException
{
// 10-Dec-2014, tatu: Not sure if this can be made to work reliably;
// but for sure delegating to default implementation will not work. So:
if (_objectIdWriter != null) {
_serializeWithObjectId(bean, gen, ctxt, typeSer);
return;
}
gen.assignCurrentValue(bean);
WritableTypeId typeIdDef = _typeIdDef(typeSer, bean, JsonToken.START_ARRAY);
typeSer.writeTypePrefix(gen, ctxt, typeIdDef);
final boolean filtered = (_filteredProps != null && ctxt.getActiveView() != null);
if (filtered) {
serializeFiltered(bean, gen, ctxt);
} else {
serializeNonFiltered(bean, gen, ctxt);
}
typeSer.writeTypeSuffix(gen, ctxt, typeIdDef);
}
/**
* Main serialization method that will delegate actual output to
* configured
* {@link BeanPropertyWriter} instances.
*/
@Override
public final void serialize(Object bean, JsonGenerator gen, SerializationContext provider)
throws JacksonException
{
final boolean filtered = (_filteredProps != null && provider.getActiveView() != null);
if (provider.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
&& hasSingleElement(provider)) {
if (filtered) serializeFiltered(bean, gen, provider);
else serializeNonFiltered(bean, gen, provider);
return;
}
// note: it is assumed here that limitations (type id, object id,
// any getter, filtering) have already been checked; so code here
// is trivial.
gen.writeStartArray(bean, _props.length);
if (filtered) serializeFiltered(bean, gen, provider);
else serializeNonFiltered(bean, gen, provider);
gen.writeEndArray();
}
/*
/**********************************************************************
/* Property serialization methods
/**********************************************************************
*/
private boolean hasSingleElement(SerializationContext provider) {
return _props.length == 1;
}
protected final void serializeNonFiltered(Object bean, JsonGenerator gen,
SerializationContext provider)
throws JacksonException
{
final BeanPropertyWriter[] props = _props;
int i = 0;
int left = props.length;
BeanPropertyWriter prop = null;
try {
if (left > 3) {
do {
prop = props[i];
prop.serializeAsElement(bean, gen, provider);
prop = props[i+1];
prop.serializeAsElement(bean, gen, provider);
prop = props[i+2];
prop.serializeAsElement(bean, gen, provider);
prop = props[i+3];
prop.serializeAsElement(bean, gen, provider);
left -= 4;
i += 4;
} while (left > 3);
}
switch (left) {
case 3:
prop = props[i++];
prop.serializeAsElement(bean, gen, provider);
case 2:
prop = props[i++];
prop.serializeAsElement(bean, gen, provider);
case 1:
prop = props[i++];
prop.serializeAsElement(bean, gen, provider);
}
// NOTE: any getters cannot be supported either
//if (_anyGetterWriter != null) {
// _anyGetterWriter.getAndSerialize(bean, gen, provider);
//}
} catch (Exception e) {
wrapAndThrow(provider, e, bean, prop.getName());
} catch (StackOverflowError e) {
throw DatabindException.from(gen, "Infinite recursion (StackOverflowError)", e)
.prependPath(bean, prop.getName());
}
}
protected final void serializeFiltered(Object bean, JsonGenerator gen, SerializationContext provider)
throws JacksonException
{
final BeanPropertyWriter[] props = _filteredProps;
int i = 0;
int left = props.length;
BeanPropertyWriter prop = null;
try {
if (left > 3) {
do {
prop = props[i];
if (prop == null) { // can have nulls in filtered list; but if so, MUST write placeholders
gen.writeNull();
} else {
prop.serializeAsElement(bean, gen, provider);
}
prop = props[i+1];
if (prop == null) {
gen.writeNull();
} else {
prop.serializeAsElement(bean, gen, provider);
}
prop = props[i+2];
if (prop == null) {
gen.writeNull();
} else {
prop.serializeAsElement(bean, gen, provider);
}
prop = props[i+3];
if (prop == null) {
gen.writeNull();
} else {
prop.serializeAsElement(bean, gen, provider);
}
left -= 4;
i += 4;
} while (left > 3);
}
switch (left) {
case 3:
prop = props[i++];
if (prop == null) {
gen.writeNull();
} else {
prop.serializeAsElement(bean, gen, provider);
}
case 2:
prop = props[i++];
if (prop == null) {
gen.writeNull();
} else {
prop.serializeAsElement(bean, gen, provider);
}
case 1:
prop = props[i++];
if (prop == null) {
gen.writeNull();
} else {
prop.serializeAsElement(bean, gen, provider);
}
}
} catch (Exception e) {
wrapAndThrow(provider, e, bean, prop.getName());
} catch (StackOverflowError e) {
throw DatabindException.from(gen, "Infinite recursion (StackOverflowError)", e)
.prependPath(bean, prop.getName());
}
}
}