StringCanonicalizingConverter.java

package tools.jackson.databind.util;

import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.SerializationContext;

/**
 * Converter that canonicalizes {@link String} values using databind's default
 * String canonicalization method.
 *<p>
 * Can be used with {@link tools.jackson.databind.annotation.JsonDeserialize#converter()}
 * or {@link tools.jackson.databind.annotation.JsonDeserialize#contentConverter()}
 * to canonicalize low-cardinality String values during deserialization.
 * Canonicalization only makes sense on deserialization, so if used on the
 * serialization side (via {@link tools.jackson.databind.annotation.JsonSerialize#converter()})
 * values are passed through as-is.
 *<p>
 * WARNING: the default canonicalization is implemented using {@link String#intern()},
 * the effects of which are JVM-wide and not limited to the on-going read.
 * Because of this, this Converter should only be applied to properties known to
 * have low cardinality (enumeration-like values, identifiers from a fixed set):
 * applying it to unbounded, caller-provided content (free-form text) allows a
 * malicious payload to degrade performance of the whole JVM.
 *
 * @since 3.3
 */
public class StringCanonicalizingConverter
    extends StdConverter<String, String>
{
    @Override
    public String convert(DeserializationContext ctxt, String value) {
        return ctxt.canonicalizeString(value);
    }

    /**
     * Serialization-side canonicalization is a no-op: the value returned is written
     * out and immediately discarded, so canonicalizing it would only add the cost
     * of {@link String#intern()} without any of the memory savings this Converter
     * exists for. Value is hence returned as-is.
     */
    @Override
    public String convert(SerializationContext ctxt, String value) {
        return value;
    }

    /**
     * Context-less conversion is not supported: canonicalization is always done
     * via {@link tools.jackson.databind.DatabindContext#canonicalizeString(String)}
     * so that the context-specific canonicalization method is used.
     * Both context-taking variants are overridden, so databind itself never calls
     * this method.
     *
     * @throws IllegalStateException Always
     */
    @Override
    public String convert(String value) {
        throw new IllegalStateException(String.format(
                "Should never be called (%s.convert(String)): must use context-taking variant",
                getClass().getName()));
    }
}