blob: f8be935277ad32b4579a92af8dc802f44494b325 [file] [log] [blame]
package com.fasterxml.jackson.databind.ser;
import java.io.IOException;
import java.io.StringWriter;
import java.util.*;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Element;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.databind.ser.std.CollectionSerializer;
import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer;
import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
import com.fasterxml.jackson.databind.util.StdConverter;
/**
* Tests for verifying various issues with custom serializers.
*/
@SuppressWarnings("serial")
public class TestCustomSerializers extends BaseMapTest
{
static class ElementSerializer extends JsonSerializer<Element>
{
@Override
public void serialize(Element value, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {
gen.writeString("element");
}
}
@JsonSerialize(using = ElementSerializer.class)
public static class ElementMixin {}
public static class Immutable {
protected int x() { return 3; }
protected int y() { return 7; }
}
/**
* Trivial simple custom escape definition set.
*/
static class CustomEscapes extends CharacterEscapes
{
private final int[] _asciiEscapes;
public CustomEscapes() {
_asciiEscapes = standardAsciiEscapesForJSON();
_asciiEscapes['a'] = 'A'; // to basically give us "\A" instead of 'a'
_asciiEscapes['b'] = CharacterEscapes.ESCAPE_STANDARD; // too force "\u0062"
}
@Override
public int[] getEscapeCodesForAscii() {
return _asciiEscapes;
}
@Override
public SerializableString getEscapeSequence(int ch) {
return null;
}
}
@JsonFormat(shape=JsonFormat.Shape.OBJECT)
static class LikeNumber extends Number {
private static final long serialVersionUID = 1L;
public int x;
public LikeNumber(int value) { x = value; }
@Override
public double doubleValue() {
return x;
}
@Override
public float floatValue() {
return x;
}
@Override
public int intValue() {
return x;
}
@Override
public long longValue() {
return x;
}
}
// for [databind#631]
static class Issue631Bean
{
@JsonSerialize(using=ParentClassSerializer.class)
public Object prop;
public Issue631Bean(Object o) {
prop = o;
}
}
static class ParentClassSerializer
extends StdScalarSerializer<Object>
{
protected ParentClassSerializer() {
super(Object.class);
}
@Override
public void serialize(Object value, JsonGenerator gen,
SerializerProvider provider) throws IOException {
Object parent = gen.getCurrentValue();
String desc = (parent == null) ? "NULL" : parent.getClass().getSimpleName();
gen.writeString(desc+"/"+value);
}
}
static class UCStringSerializer extends StdScalarSerializer<String>
{
public UCStringSerializer() { super(String.class); }
@Override
public void serialize(String value, JsonGenerator gen,
SerializerProvider provider) throws IOException {
gen.writeString(value.toUpperCase());
}
}
// IMPORTANT: must associate serializer via property annotations
protected static class StringListWrapper
{
@JsonSerialize(contentUsing=UCStringSerializer.class)
public List<String> list;
public StringListWrapper(String... values) {
list = new ArrayList<>();
for (String value : values) {
list.add(value);
}
}
}
// [databind#2475]
static class MyFilter2475 extends SimpleBeanPropertyFilter {
@Override
public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception {
// Ensure that "current value" remains pojo
final JsonStreamContext ctx = jgen.getOutputContext();
final Object curr = ctx.getCurrentValue();
if (!(curr instanceof Item2475)) {
throw new Error("Field '"+writer.getName()+"', context not that of `Item2475` instance");
}
super.serializeAsField(pojo, jgen, provider, writer);
}
}
@JsonFilter("myFilter")
@JsonPropertyOrder({ "id", "set" })
public static class Item2475 {
private Collection<String> set;
private String id;
public Item2475(Collection<String> set, String id) {
this.set = set;
this.id = id;
}
public Collection<String> getSet() {
return set;
}
public String getId() {
return id;
}
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
private final ObjectMapper MAPPER = new ObjectMapper();
public void testCustomization() throws Exception
{
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(Element.class, ElementMixin.class);
Element element = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().createElement("el");
StringWriter sw = new StringWriter();
objectMapper.writeValue(sw, element);
assertEquals(sw.toString(), "\"element\"");
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public void testCustomLists() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
JsonSerializer<?> ser = new CollectionSerializer(null, false, null, null);
final JsonSerializer<Object> collectionSerializer = (JsonSerializer<Object>) ser;
module.addSerializer(Collection.class, new JsonSerializer<Collection>() {
@Override
public void serialize(Collection value, JsonGenerator gen, SerializerProvider provider)
throws IOException
{
if (value.size() != 0) {
collectionSerializer.serialize(value, gen, provider);
} else {
gen.writeNull();
}
}
});
mapper.registerModule(module);
assertEquals("null", mapper.writeValueAsString(new ArrayList<Object>()));
}
// [databind#87]: delegating serializer
public void testDelegating() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(new StdDelegatingSerializer(Immutable.class,
new StdConverter<Immutable, Map<String,Integer>>() {
@Override
public Map<String, Integer> convert(Immutable value)
{
HashMap<String,Integer> map = new LinkedHashMap<String,Integer>();
map.put("x", value.x());
map.put("y", value.y());
return map;
}
}));
mapper.registerModule(module);
assertEquals("{\"x\":3,\"y\":7}", mapper.writeValueAsString(new Immutable()));
}
// [databind#215]: Allow registering CharacterEscapes via ObjectWriter
public void testCustomEscapes() throws Exception
{
assertEquals(quote("foo\\u0062\\Ar"),
MAPPER.writer(new CustomEscapes()).writeValueAsString("foobar"));
}
public void testNumberSubclass() throws Exception
{
assertEquals(aposToQuotes("{'x':42}"),
MAPPER.writeValueAsString(new LikeNumber(42)));
}
public void testWithCurrentValue() throws Exception
{
assertEquals(aposToQuotes("{'prop':'Issue631Bean/42'}"),
MAPPER.writeValueAsString(new Issue631Bean(42)));
}
public void testWithCustomElements() throws Exception
{
// First variant that uses per-property override
StringListWrapper wr = new StringListWrapper("a", null, "b");
assertEquals(aposToQuotes("{'list':['A',null,'B']}"),
MAPPER.writeValueAsString(wr));
// and then per-type registration
SimpleModule module = new SimpleModule("test", Version.unknownVersion());
module.addSerializer(String.class, new UCStringSerializer());
ObjectMapper mapper = new ObjectMapper()
.registerModule(module);
assertEquals(quote("FOOBAR"), mapper.writeValueAsString("foobar"));
assertEquals(aposToQuotes("['FOO',null]"),
mapper.writeValueAsString(new String[] { "foo", null }));
List<String> list = Arrays.asList("foo", null);
assertEquals(aposToQuotes("['FOO',null]"), mapper.writeValueAsString(list));
Set<String> set = new LinkedHashSet<String>(Arrays.asList("foo", null));
assertEquals(aposToQuotes("['FOO',null]"), mapper.writeValueAsString(set));
}
// [databind#2475]
public void testIssue2475() throws Exception {
SimpleFilterProvider provider = new SimpleFilterProvider().addFilter("myFilter", new MyFilter2475());
ObjectWriter writer = MAPPER.writer(provider);
// contents don't really matter that much as verification within filter but... let's
// check anyway
assertEquals(aposToQuotes("{'id':'ID-1','set':[]}"),
writer.writeValueAsString(new Item2475(new ArrayList<String>(), "ID-1")));
assertEquals(aposToQuotes("{'id':'ID-2','set':[]}"),
writer.writeValueAsString(new Item2475(new HashSet<String>(), "ID-2")));
}
}