All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fitbur.jackson.databind.ser.std.JsonValueSerializer Maven / Gradle / Ivy

package com.fitbur.jackson.databind.ser.std;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.LinkedHashSet;
import java.util.Set;

import com.fitbur.jackson.core.*;
import com.fitbur.jackson.databind.*;
import com.fitbur.jackson.databind.annotation.JacksonStdImpl;
import com.fitbur.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
import com.fitbur.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fitbur.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor;
import com.fitbur.jackson.databind.jsonschema.SchemaAware;
import com.fitbur.jackson.databind.jsontype.TypeSerializer;
import com.fitbur.jackson.databind.ser.BeanSerializer;
import com.fitbur.jackson.databind.ser.ContextualSerializer;

/**
 * Serializer class that can serialize Object that have a
 * {@link com.fitbur.jackson.annotation.JsonValue} annotation to
 * indicate that serialization should be done by calling the method
 * annotated, and serializing result it returns.
 *

* Implementation note: we will post-process resulting serializer * (much like what is done with {@link BeanSerializer}) * to figure out actual serializers for final types. * This must be done from {@link #createContextual} method, and NOT from constructor; * otherwise we could end up with an infinite loop. */ @SuppressWarnings("serial") @JacksonStdImpl public class JsonValueSerializer extends StdSerializer implements ContextualSerializer, JsonFormatVisitable, SchemaAware { protected final Method _accessorMethod; protected final JsonSerializer _valueSerializer; protected final BeanProperty _property; /** * This is a flag that is set in rare (?) cases where this serializer * is used for "natural" types (boolean, int, String, double); and where * we actually must force type information wrapping, even though * one would not normally be added. */ protected final boolean _forceTypeInformation; /* /********************************************************** /* Life-cycle /********************************************************** */ /** * @param ser Explicit serializer to use, if caller knows it (which * occurs if and only if the "value method" was annotated with * {@link com.fitbur.jackson.databind.annotation.JsonSerialize#using}), otherwise * null */ @SuppressWarnings("unchecked") public JsonValueSerializer(Method valueMethod, JsonSerializer ser) { super(valueMethod.getReturnType(), false); _accessorMethod = valueMethod; _valueSerializer = (JsonSerializer) ser; _property = null; _forceTypeInformation = true; // gets reconsidered when we are contextualized } @SuppressWarnings("unchecked") public JsonValueSerializer(JsonValueSerializer src, BeanProperty property, JsonSerializer ser, boolean forceTypeInfo) { super(_notNullClass(src.handledType())); _accessorMethod = src._accessorMethod; _valueSerializer = (JsonSerializer) ser; _property = property; _forceTypeInformation = forceTypeInfo; } @SuppressWarnings("unchecked") private final static Class _notNullClass(Class cls) { return (cls == null) ? Object.class : (Class) cls; } public JsonValueSerializer withResolved(BeanProperty property, JsonSerializer ser, boolean forceTypeInfo) { if (_property == property && _valueSerializer == ser && forceTypeInfo == _forceTypeInformation) { return this; } return new JsonValueSerializer(this, property, ser, forceTypeInfo); } /* /********************************************************** /* Post-processing /********************************************************** */ /** * We can try to find the actual serializer for value, if we can * statically figure out what the result type must be. */ @Override public JsonSerializer createContextual(SerializerProvider provider, BeanProperty property) throws JsonMappingException { JsonSerializer ser = _valueSerializer; if (ser == null) { /* Can only assign serializer statically if the declared type is final: * if not, we don't really know the actual type until we get the instance. */ // 10-Mar-2010, tatu: Except if static typing is to be used if (provider.isEnabled(MapperFeature.USE_STATIC_TYPING) || Modifier.isFinal(_accessorMethod.getReturnType().getModifiers())) { JavaType t = provider.constructType(_accessorMethod.getGenericReturnType()); // false -> no need to cache /* 10-Mar-2010, tatu: Ideally we would actually separate out type * serializer from value serializer; but, alas, there's no access * to serializer factory at this point... */ // 05-Sep-2013, tatu: I _think_ this can be considered a primary property... ser = provider.findPrimaryPropertySerializer(t, property); /* 09-Dec-2010, tatu: Turns out we must add special handling for * cases where "native" (aka "natural") type is being serialized, * using standard serializer */ boolean forceTypeInformation = isNaturalTypeWithStdHandling(t.getRawClass(), ser); return withResolved(property, ser, forceTypeInformation); } } else { // 05-Sep-2013, tatu: I _think_ this can be considered a primary property... ser = provider.handlePrimaryContextualization(ser, property); return withResolved(property, ser, _forceTypeInformation); } return this; } /* /********************************************************** /* Actual serialization /********************************************************** */ @Override public void serialize(Object bean, JsonGenerator jgen, SerializerProvider prov) throws IOException { try { Object value = _accessorMethod.invoke(bean); if (value == null) { prov.defaultSerializeNull(jgen); return; } JsonSerializer ser = _valueSerializer; if (ser == null) { Class c = value.getClass(); /* 10-Mar-2010, tatu: Ideally we would actually separate out type * serializer from value serializer; but, alas, there's no access * to serializer factory at this point... */ // let's cache it, may be needed soon again ser = prov.findTypedValueSerializer(c, true, _property); } ser.serialize(value, jgen, prov); } catch (IOException ioe) { throw ioe; } catch (Exception e) { Throwable t = e; // Need to unwrap this specific type, to see infinite recursion... while (t instanceof InvocationTargetException && t.getCause() != null) { t = t.getCause(); } // Errors shouldn't be wrapped (and often can't, as well) if (t instanceof Error) { throw (Error) t; } // let's try to indicate the path best we can... throw JsonMappingException.wrapWithPath(t, bean, _accessorMethod.getName() + "()"); } } @Override public void serializeWithType(Object bean, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer0) throws IOException { // Regardless of other parts, first need to find value to serialize: Object value = null; try { value = _accessorMethod.invoke(bean); // and if we got null, can also just write it directly if (value == null) { provider.defaultSerializeNull(jgen); return; } JsonSerializer ser = _valueSerializer; if (ser == null) { // already got a serializer? fabulous, that be easy... // ser = provider.findTypedValueSerializer(value.getClass(), true, _property); ser = provider.findValueSerializer(value.getClass(), _property); } else { /* 09-Dec-2010, tatu: To work around natural type's refusal to add type info, we do * this (note: type is for the wrapper type, not enclosed value!) */ if (_forceTypeInformation) { typeSer0.writeTypePrefixForScalar(bean, jgen); ser.serialize(value, jgen, provider); typeSer0.writeTypeSuffixForScalar(bean, jgen); return; } } /* 13-Feb-2013, tatu: Turns out that work-around should NOT be required * at all; it would not lead to correct behavior (as per #167). */ // and then redirect type id lookups // TypeSerializer typeSer = new TypeSerializerWrapper(typeSer0, bean); ser.serializeWithType(value, jgen, provider, typeSer0); } catch (IOException ioe) { throw ioe; } catch (Exception e) { Throwable t = e; // Need to unwrap this specific type, to see infinite recursion... while (t instanceof InvocationTargetException && t.getCause() != null) { t = t.getCause(); } // Errors shouldn't be wrapped (and often can't, as well) if (t instanceof Error) { throw (Error) t; } // let's try to indicate the path best we can... throw JsonMappingException.wrapWithPath(t, bean, _accessorMethod.getName() + "()"); } } @SuppressWarnings("deprecation") @Override public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException { if (_valueSerializer instanceof SchemaAware) { return ((SchemaAware)_valueSerializer).getSchema(provider, null); } return com.fitbur.jackson.databind.jsonschema.JsonSchema.getDefaultSchemaNode(); } @Override public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException { /* 27-Apr-2015, tatu: First things first; for JSON Schema introspection, * Enums are special, and unfortunately we will need to add special * handling here (see https://github.com/FasterXML/jackson-module-jsonSchema/issues/57 * for details). */ Class decl = (typeHint == null) ? null : typeHint.getRawClass(); if (decl == null) { decl = _accessorMethod.getDeclaringClass(); } if ((decl != null) && (decl.isEnum())) { if (_acceptJsonFormatVisitorForEnum(visitor, typeHint, decl)) { return; } } JsonSerializer ser = _valueSerializer; if (ser == null) { if (typeHint == null) { if (_property != null) { typeHint = _property.getType(); } if (typeHint == null) { typeHint = visitor.getProvider().constructType(_handledType); } } ser = visitor.getProvider().findTypedValueSerializer(typeHint, false, _property); if (ser == null) { visitor.expectAnyFormat(typeHint); return; } } ser.acceptJsonFormatVisitor(visitor, null); } /** * Overridable helper method used for special case handling of schema information for * Enums * * @return True if method handled callbacks; false if not; in latter case caller will * send default callbacks * * @since 2.6 */ protected boolean _acceptJsonFormatVisitorForEnum(JsonFormatVisitorWrapper visitor, JavaType typeHint, Class enumType) throws JsonMappingException { // Copied from EnumSerializer#acceptJsonFormatVisitor JsonStringFormatVisitor stringVisitor = visitor.expectStringFormat(typeHint); if (stringVisitor != null) { Set enums = new LinkedHashSet(); for (Object en : enumType.getEnumConstants()) { try { enums.add(String.valueOf(_accessorMethod.invoke(en))); } catch (Exception e) { Throwable t = e; while (t instanceof InvocationTargetException && t.getCause() != null) { t = t.getCause(); } if (t instanceof Error) { throw (Error) t; } throw JsonMappingException.wrapWithPath(t, en, _accessorMethod.getName() + "()"); } } stringVisitor.enumTypes(enums); } return true; } protected boolean isNaturalTypeWithStdHandling(Class rawType, JsonSerializer ser) { // First: do we have a natural type being handled? if (rawType.isPrimitive()) { if (rawType != Integer.TYPE && rawType != Boolean.TYPE && rawType != Double.TYPE) { return false; } } else { if (rawType != String.class && rawType != Integer.class && rawType != Boolean.class && rawType != Double.class) { return false; } } return isDefaultSerializer(ser); } /* /********************************************************** /* Other methods /********************************************************** */ @Override public String toString() { return "(@JsonValue serializer for method " + _accessorMethod.getDeclaringClass() + "#" + _accessorMethod.getName() + ")"; } }