JsonSerializeTest.java
package tools.jackson.databind.ser;
import java.io.IOException;
import java.util.*;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonSerializeAs;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.ser.std.NullSerializer;
import tools.jackson.databind.ser.std.StdSerializer;
import tools.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.*;
/**
* This unit test suite tests use of {@link JsonSerialize} annotation
* with bean serialization.
*/
@SuppressWarnings("serial")
public class JsonSerializeTest
extends DatabindTestUtil
{
/*
/**********************************************************
/* Helper types for basic @JsonSerialize tests
/**********************************************************
*/
interface ValueInterface {
public int getX();
}
static class ValueClass
implements ValueInterface
{
@Override
public int getX() { return 3; }
public int getY() { return 5; }
}
/**
* Test class to verify that <code>JsonSerialize.as</code>
* works as expected
*/
static class WrapperClassForAs
{
@JsonSerialize(as=ValueInterface.class)
public ValueClass getValue() {
return new ValueClass();
}
}
// This should indicate that static type be used for all fields
@JsonSerialize(typing=JsonSerialize.Typing.STATIC)
static class WrapperClassForStaticTyping
{
public ValueInterface getValue() {
return new ValueClass();
}
}
static class WrapperClassForStaticTyping2
{
@JsonSerialize(typing=JsonSerialize.Typing.STATIC)
public ValueInterface getStaticValue() {
return new ValueClass();
}
@JsonSerialize(typing=JsonSerialize.Typing.DYNAMIC)
public ValueInterface getDynamicValue() {
return new ValueClass();
}
}
/**
* Test bean that has an invalid {@link JsonSerialize} annotation.
*/
static class BrokenClass
{
// invalid annotation: String not a supertype of Long
@JsonSerialize(as=String.class)
public Long getValue() {
return Long.valueOf(4L);
}
}
static class ValueMap extends HashMap<String,ValueInterface> { }
static class ValueList extends ArrayList<ValueInterface> { }
static class ValueLinkedList extends LinkedList<ValueInterface> { }
static class Foo294
{
@JsonProperty private String id;
@JsonSerialize(using = Bar294Serializer.class)
private Bar294 bar;
public Foo294() { }
protected Foo294(String id, String id2) {
this.id = id;
bar = new Bar294(id2);
}
}
static class Bar294{
@JsonProperty protected String id;
@JsonProperty protected String name;
public Bar294() { }
public Bar294(String id) {
this.id = id;
}
public String getId() { return id; }
public String getName() { return name; }
}
static class Bar294Serializer extends StdSerializer<Bar294>
{
public Bar294Serializer() { super(Bar294.class); }
@Override
public void serialize(Bar294 bar, JsonGenerator g,
SerializationContext provider)
{
g.writeString(bar.id);
}
}
@JsonPropertyOrder({ "a", "something" })
static class Response {
public String a = "x";
@JsonProperty //does not show up
public boolean isSomething() { return true; }
}
/*
/**********************************************************
/* Helper types for contentAs/contentUsing/keyUsing tests
/**********************************************************
*/
static class SimpleKey {
protected final String key;
public SimpleKey(String str) { key = str; }
@Override public String toString() { return "toString:"+key; }
}
static class SimpleValue {
public final String value;
public SimpleValue(String str) { value = str; }
}
@JsonPropertyOrder({"value", "value2"})
static class ActualValue extends SimpleValue
{
public final String other = "123";
public ActualValue(String str) { super(str); }
}
static class SimpleKeySerializer extends ValueSerializer<SimpleKey> {
@Override
public void serialize(SimpleKey key, JsonGenerator g, SerializationContext provider)
{
g.writeName("key "+key.key);
}
}
static class SimpleValueSerializer extends ValueSerializer<SimpleValue> {
@Override
public void serialize(SimpleValue value, JsonGenerator g, SerializationContext provider)
{
g.writeString("value "+value.value);
}
}
@JsonSerialize(contentAs=SimpleValue.class)
static class SimpleValueList extends ArrayList<ActualValue> { }
@JsonSerialize(contentAs=SimpleValue.class)
static class SimpleValueMap extends HashMap<SimpleKey, ActualValue> { }
@JsonSerialize(contentUsing=SimpleValueSerializer.class)
static class SimpleValueListWithSerializer extends ArrayList<ActualValue> { }
@JsonSerialize(keyUsing=SimpleKeySerializer.class, contentUsing=SimpleValueSerializer.class)
static class SimpleValueMapWithSerializer extends HashMap<SimpleKey, ActualValue> { }
static class ListWrapperSimple
{
@JsonSerialize(contentAs=SimpleValue.class)
public final ArrayList<ActualValue> values = new ArrayList<ActualValue>();
public ListWrapperSimple(String value) {
values.add(new ActualValue(value));
}
}
static class ListWrapperWithSerializer
{
@JsonSerialize(contentUsing=SimpleValueSerializer.class)
public final ArrayList<ActualValue> values = new ArrayList<ActualValue>();
public ListWrapperWithSerializer(String value) {
values.add(new ActualValue(value));
}
}
static class MapWrapperSimple
{
@JsonSerialize(contentAs=SimpleValue.class)
public final HashMap<SimpleKey, ActualValue> values = new HashMap<SimpleKey, ActualValue>();
public MapWrapperSimple(String key, String value) {
values.put(new SimpleKey(key), new ActualValue(value));
}
}
static class MapWrapperWithSerializer
{
@JsonSerialize(keyUsing=SimpleKeySerializer.class, contentUsing=SimpleValueSerializer.class)
public final HashMap<SimpleKey, ActualValue> values = new HashMap<SimpleKey, ActualValue>();
public MapWrapperWithSerializer(String key, String value) {
values.put(new SimpleKey(key), new ActualValue(value));
}
}
static class NullBean
{
@JsonSerialize(using=NullSerializer.class)
public String value = "abc";
}
/*
/**********************************************************
/* Helper types for contentUsing on list elements
/**********************************************************
*/
// [JACKSON-829]
static class FooToBarSerializer extends ValueSerializer<String> {
@Override
public void serialize(String value, JsonGenerator g, SerializationContext provider)
{
if ("foo".equals(value)) {
g.writeString("bar");
} else {
g.writeString(value);
}
}
}
static class MyObject {
@JsonSerialize(contentUsing = FooToBarSerializer.class)
List<String> list;
}
/*
/**********************************************************************
/* Helper types for @JsonSerializeAs (new annotation)
/**********************************************************************
*/
public interface Fooable {
public int getFoo();
}
// force use of interface
@JsonSerializeAs(Fooable.class)
public static class FooImpl implements Fooable {
@Override
public int getFoo() { return 42; }
public int getBar() { return 15; }
}
static class FooImplNoAnno implements Fooable {
@Override
public int getFoo() { return 42; }
public int getBar() { return 15; }
}
public class Fooables {
public FooImpl[] getFoos() {
return new FooImpl[] { new FooImpl() };
}
}
public class FooableWrapper {
public FooImpl getFoo() {
return new FooImpl();
}
}
static class FooableWithFieldWrapper {
@JsonSerializeAs(Fooable.class)
public Fooable getFoo() {
return new FooImplNoAnno();
}
}
interface Bean5476Base {
public int getA();
}
@JsonPropertyOrder({"a","b"})
static abstract class Bean5476Abstract implements Bean5476Base {
@Override
public int getA() { return 1; }
public int getB() { return 2; }
}
static class Bean5476Impl extends Bean5476Abstract {
public int getC() { return 3; }
}
static class Bean5476Wrapper {
@JsonSerializeAs(content=Bean5476Abstract.class)
public List<Bean5476Base> values;
public Bean5476Wrapper(int count) {
values = new ArrayList<Bean5476Base>();
for (int i = 0; i < count; ++i) {
values.add(new Bean5476Impl());
}
}
}
static class Bean5476Holder {
@JsonSerializeAs(Bean5476Abstract.class)
public Bean5476Base value = new Bean5476Impl();
}
interface MapKeyBase {
String getId();
}
@JsonPropertyOrder({"id"})
static abstract class MapKeyAbstract implements MapKeyBase {
@Override
public String getId() { return "key"; }
}
static class MapKeyImpl extends MapKeyAbstract {
public String getExtra() { return "extra"; }
}
static class MapKeyWrapper {
@JsonSerializeAs(key=MapKeyAbstract.class)
public Map<MapKeyBase, String> values;
public MapKeyWrapper() {
values = new LinkedHashMap<>();
values.put(new MapKeyImpl(), "value1");
}
}
/*
/**********************************************************************
/* Helper types for legacy @JsonSerialize(as=...)
/**********************************************************************
*/
public interface LegacyFooable {
public int getFoo();
}
// force use of interface
@JsonSerialize(as=LegacyFooable.class)
public static class LegacyFooImpl implements LegacyFooable {
@Override
public int getFoo() { return 42; }
public int getBar() { return 15; }
}
static class LegacyFooImplNoAnno implements LegacyFooable {
@Override
public int getFoo() { return 42; }
public int getBar() { return 15; }
}
public class LegacyFooables {
public LegacyFooImpl[] getFoos() {
return new LegacyFooImpl[] { new LegacyFooImpl() };
}
}
public class LegacyFooableWrapper {
public LegacyFooImpl getFoo() {
return new LegacyFooImpl();
}
}
// for [databind#1023]
static class LegacyFooableWithFieldWrapper {
@JsonSerialize(as=LegacyFooable.class)
public LegacyFooable getFoo() {
return new LegacyFooImplNoAnno();
}
}
interface Bean1178Base {
public int getA();
}
@JsonPropertyOrder({"a","b"})
static abstract class Bean1178Abstract implements Bean1178Base {
@Override
public int getA() { return 1; }
public int getB() { return 2; }
}
static class Bean1178Impl extends Bean1178Abstract {
public int getC() { return 3; }
}
static class Bean1178Wrapper {
@JsonSerialize(contentAs=Bean1178Abstract.class)
public List<Bean1178Base> values;
public Bean1178Wrapper(int count) {
values = new ArrayList<Bean1178Base>();
for (int i = 0; i < count; ++i) {
values.add(new Bean1178Impl());
}
}
}
static class Bean1178Holder {
@JsonSerialize(as=Bean1178Abstract.class)
public Bean1178Base value = new Bean1178Impl();
}
/*
/**********************************************************
/* Test methods, basic @JsonSerialize
/**********************************************************
*/
final ObjectMapper MAPPER = newJsonMapper();
private final ObjectMapper STATIC_MAPPER = jsonMapperBuilder()
.enable(MapperFeature.USE_STATIC_TYPING)
.build();
private final ObjectWriter WRITER = objectWriter();
@SuppressWarnings("unchecked")
@Test
public void testSimpleValueDefinition() throws Exception
{
Map<String,Object> result = writeAndMap(MAPPER, new WrapperClassForAs());
assertEquals(1, result.size());
Object ob = result.get("value");
// Should see only "x", not "y"
result = (Map<String,Object>) ob;
assertEquals(1, result.size());
assertEquals(Integer.valueOf(3), result.get("x"));
}
@Test
public void testBrokenAnnotation() throws Exception
{
try {
MAPPER.writeValueAsString(new BrokenClass());
fail("Should not succeed");
} catch (Exception e) {
verifyException(e, "types not related");
}
}
@SuppressWarnings("unchecked")
@Test
public void testStaticTypingForClass() throws Exception
{
Map<String,Object> result = writeAndMap(MAPPER, new WrapperClassForStaticTyping());
assertEquals(1, result.size());
Object ob = result.get("value");
// Should see only "x", not "y"
result = (Map<String,Object>) ob;
assertEquals(1, result.size());
assertEquals(Integer.valueOf(3), result.get("x"));
}
@SuppressWarnings("unchecked")
@Test
public void testMixedTypingForClass() throws Exception
{
Map<String,Object> result = writeAndMap(MAPPER, new WrapperClassForStaticTyping2());
assertEquals(2, result.size());
Object obStatic = result.get("staticValue");
// Should see only "x", not "y"
Map<String,Object> stat = (Map<String,Object>) obStatic;
assertEquals(1, stat.size());
assertEquals(Integer.valueOf(3), stat.get("x"));
Object obDynamic = result.get("dynamicValue");
// Should see both
Map<String,Object> dyn = (Map<String,Object>) obDynamic;
assertEquals(2, dyn.size());
assertEquals(Integer.valueOf(3), dyn.get("x"));
assertEquals(Integer.valueOf(5), dyn.get("y"));
}
@Test
public void testStaticTypingWithMap() throws Exception
{
ValueMap map = new ValueMap();
map.put("a", new ValueClass());
assertEquals("{\"a\":{\"x\":3}}", STATIC_MAPPER.writeValueAsString(map));
}
@Test
public void testStaticTypingWithArrayList() throws Exception
{
ValueList list = new ValueList();
list.add(new ValueClass());
assertEquals("[{\"x\":3}]", STATIC_MAPPER.writeValueAsString(list));
}
@Test
public void testStaticTypingWithLinkedList() throws Exception
{
ValueLinkedList list = new ValueLinkedList();
list.add(new ValueClass());
assertEquals("[{\"x\":3}]", STATIC_MAPPER.writeValueAsString(list));
}
@Test
public void testStaticTypingWithArray() throws Exception
{
ValueInterface[] array = new ValueInterface[] { new ValueClass() };
assertEquals("[{\"x\":3}]", STATIC_MAPPER.writeValueAsString(array));
}
@Test
public void testIssue294() throws Exception
{
JsonMapper mapper = JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
assertEquals("{\"bar\":\"barId\",\"id\":\"fooId\"}",
mapper.writeValueAsString(new Foo294("fooId", "barId")));
}
@Test
public void testWithIsGetter() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.changeDefaultVisibility(vc -> vc
.withVisibility(PropertyAccessor.GETTER, Visibility.NONE)
.withVisibility(PropertyAccessor.FIELD, Visibility.ANY)
.withVisibility(PropertyAccessor.CREATOR, Visibility.NONE)
.withVisibility(PropertyAccessor.IS_GETTER, Visibility.NONE)
.withVisibility(PropertyAccessor.SETTER, Visibility.NONE))
.build();
assertEquals(a2q("{'a':'x','something':true}"),
mapper.writeValueAsString(new Response()));
}
/*
/**********************************************************
/* Test methods, contentAs/contentUsing/keyUsing
/**********************************************************
*/
// test value annotation applied to List value class
@Test
public void testSerializedAsListWithClassAnnotations() throws IOException
{
SimpleValueList list = new SimpleValueList();
list.add(new ActualValue("foo"));
assertEquals("[{\"value\":\"foo\"}]", MAPPER.writeValueAsString(list));
}
// test value annotation applied to Map value class
@Test
public void testSerializedAsMapWithClassAnnotations() throws IOException
{
SimpleValueMap map = new SimpleValueMap();
map.put(new SimpleKey("x"), new ActualValue("y"));
assertEquals("{\"toString:x\":{\"value\":\"y\"}}", MAPPER.writeValueAsString(map));
}
// test Serialization annotation with List
@Test
public void testSerializedAsListWithClassSerializer() throws IOException
{
SimpleValueListWithSerializer list = new SimpleValueListWithSerializer();
list.add(new ActualValue("foo"));
assertEquals("[\"value foo\"]", MAPPER.writeValueAsString(list));
}
@Test
public void testSerializedAsListWithPropertyAnnotations() throws IOException
{
ListWrapperSimple input = new ListWrapperSimple("bar");
assertEquals("{\"values\":[{\"value\":\"bar\"}]}", MAPPER.writeValueAsString(input));
}
@Test
public void testSerializedAsMapWithClassSerializer() throws IOException
{
SimpleValueMapWithSerializer map = new SimpleValueMapWithSerializer();
map.put(new SimpleKey("abc"), new ActualValue("123"));
assertEquals("{\"key abc\":\"value 123\"}", MAPPER.writeValueAsString(map));
}
@Test
public void testSerializedAsMapWithPropertyAnnotations() throws IOException
{
MapWrapperSimple input = new MapWrapperSimple("a", "b");
assertEquals("{\"values\":{\"toString:a\":{\"value\":\"b\"}}}",
MAPPER.writeValueAsString(input));
}
@Test
public void testSerializedAsListWithPropertyAnnotations2() throws IOException
{
ListWrapperWithSerializer input = new ListWrapperWithSerializer("abc");
assertEquals("{\"values\":[\"value abc\"]}", MAPPER.writeValueAsString(input));
}
@Test
public void testSerializedAsMapWithPropertyAnnotations2() throws IOException
{
MapWrapperWithSerializer input = new MapWrapperWithSerializer("foo", "b");
assertEquals("{\"values\":{\"key foo\":\"value b\"}}", MAPPER.writeValueAsString(input));
}
@Test
public void testEmptyInclusionContainers() throws IOException
{
ObjectMapper defMapper = MAPPER;
ObjectMapper inclMapper = jsonMapperBuilder()
.changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_EMPTY))
.build();
ListWrapper<String> list = new ListWrapper<String>();
assertEquals("{\"list\":[]}", defMapper.writeValueAsString(list));
assertEquals("{}", inclMapper.writeValueAsString(list));
assertEquals("{}", inclMapper.writeValueAsString(new ListWrapper<String>()));
MapWrapper<String,Integer> map = new MapWrapper<String,Integer>(new HashMap<String,Integer>());
assertEquals("{\"map\":{}}", defMapper.writeValueAsString(map));
assertEquals("{}", inclMapper.writeValueAsString(map));
assertEquals("{}", inclMapper.writeValueAsString(new MapWrapper<String,Integer>(null)));
ArrayWrapper<Integer> array = new ArrayWrapper<Integer>(new Integer[0]);
assertEquals("{\"array\":[]}", defMapper.writeValueAsString(array));
assertEquals("{}", inclMapper.writeValueAsString(array));
assertEquals("{}", inclMapper.writeValueAsString(new ArrayWrapper<Integer>(null)));
}
@Test
public void testNullSerializer() throws Exception
{
assertEquals("{\"value\":null}", MAPPER.writeValueAsString(new NullBean()));
}
/*
/**********************************************************
/* Test methods, contentUsing on list elements
/**********************************************************
*/
@Test
public void testCustomContentSerializer() throws Exception
{
MyObject object = new MyObject();
object.list = Arrays.asList("foo");
String json = MAPPER.writeValueAsString(object);
assertEquals("{\"list\":[\"bar\"]}", json);
}
/*
/**********************************************************************
/* Test methods, @JsonSerializeAs (new annotation)
/**********************************************************************
*/
@Test
public void testSerializeAsInClass() throws Exception {
assertEquals("{\"foo\":42}", WRITER.writeValueAsString(new FooImpl()));
}
@Test
public void testSerializeAsForArrayProp() throws Exception {
assertEquals("{\"foos\":[{\"foo\":42}]}",
WRITER.writeValueAsString(new Fooables()));
}
@Test
public void testSerializeAsForSimpleProp() throws Exception {
assertEquals("{\"foo\":{\"foo\":42}}",
WRITER.writeValueAsString(new FooableWrapper()));
}
@Test
public void testSerializeWithFieldAnno() throws Exception {
assertEquals("{\"foo\":{\"foo\":42}}",
WRITER.writeValueAsString(new FooableWithFieldWrapper()));
}
// Test for content parameter
@Test
public void testSpecializedContentAs() throws Exception {
assertEquals(a2q("{'values':[{'a':1,'b':2}]}"),
WRITER.writeValueAsString(new Bean5476Wrapper(1)));
}
// Test for value parameter
@Test
public void testSpecializedAsIntermediate() throws Exception {
assertEquals(a2q("{'value':{'a':1,'b':2}}"),
WRITER.writeValueAsString(new Bean5476Holder()));
}
// Test for key parameter
@Test
public void testSpecializedKeyAs() throws Exception {
String json = WRITER.writeValueAsString(new MapKeyWrapper());
assertTrue(json.contains("\"values\""), "Should contain 'values' field");
}
/*
/**********************************************************************
/* Test methods, legacy @JsonSerialize(as=...)
/**********************************************************************
*/
@Test
public void testLegacySerializeAsInClass() throws Exception {
assertEquals("{\"foo\":42}", WRITER.writeValueAsString(new LegacyFooImpl()));
}
@Test
public void testLegacySerializeAsForArrayProp() throws Exception {
assertEquals("{\"foos\":[{\"foo\":42}]}",
WRITER.writeValueAsString(new LegacyFooables()));
}
@Test
public void testLegacySerializeAsForSimpleProp() throws Exception {
assertEquals("{\"foo\":{\"foo\":42}}",
WRITER.writeValueAsString(new LegacyFooableWrapper()));
}
// for [databind#1023]
@Test
public void testLegacySerializeWithFieldAnno() throws Exception {
assertEquals("{\"foo\":{\"foo\":42}}",
WRITER.writeValueAsString(new LegacyFooableWithFieldWrapper()));
}
// for [databind#1178]
@Test
public void testLegacySpecializedContentAs1178() throws Exception {
assertEquals(a2q("{'values':[{'a':1,'b':2}]}"),
WRITER.writeValueAsString(new Bean1178Wrapper(1)));
}
// for [databind#1231] (and continuation of [databind#1178])
@Test
public void testLegacySpecializedAsIntermediate1231() throws Exception {
assertEquals(a2q("{'value':{'a':1,'b':2}}"),
WRITER.writeValueAsString(new Bean1178Holder()));
}
}