package com.fasterxml.jackson.databind.ser;
import java.io.IOException;
import java.util.*;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonGenerator;
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.std.StdScalarSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
/**
* This unit test suite tests functioning of {@link JsonValue}
* annotation with bean serialization.
*/
@SuppressWarnings("serial")
public class JsonValueTest
extends BaseMapTest
{
static class ValueClass<T>
{
final T _value;
public ValueClass(T v) { _value = v; }
@JsonValue T value() { return _value; }
}
static class FieldValueClass<T>
{
@JsonValue(true)
final T _value;
public FieldValueClass(T v) { _value = v; }
}
/**
* Another test class to check that it is also possible to
* force specific serializer to use with @JsonValue annotated
* method. Difference is between Integer serialization, and
* conversion to a Json String.
*/
final static class ToStringValueClass<T>
extends ValueClass<T>
{
public ToStringValueClass(T value) { super(value); }
// Also, need to use this annotation to help
@JsonSerialize(using=ToStringSerializer.class)
@Override
@JsonValue T value() { return super.value(); }
}
final static class ToStringValueClass2
extends ValueClass<String>
{
public ToStringValueClass2(String value) { super(value); }
// Simple as well, but let's ensure that other getters won't matter...
@JsonProperty int getFoobar() { return 4; }
public String[] getSomethingElse() { return new String[] { "1", "a" }; }
}
static class ValueBase {
public String a = "a";
}
static class ValueType extends ValueBase {
public String b = "b";
}
// Finally, let's also test static vs dynamic type
static class ValueWrapper {
@JsonValue
public ValueBase getX() { return new ValueType(); }
}
static class MapBean
{
@JsonValue
public Map<String,String> toMap()
{
HashMap<String,String> map = new HashMap<String,String>();
map.put("a", "1");
return map;
}
}
static class MapFieldBean
{
@JsonValue
Map<String,String> stuff = new HashMap<>();
{
stuff.put("b", "2");
}
}
static class MapAsNumber extends HashMap<String,String>
{
@JsonValue
public int value() { return 42; }
}
static class ListAsNumber extends ArrayList<Integer>
{
@JsonValue
public int value() { return 13; }
}
// Just to ensure it's possible to disable annotation (usually
// via mix-ins, but here directly)
@JsonPropertyOrder({ "x", "y" })
static class DisabledJsonValue {
@JsonValue(false)
public int x = 1;
@JsonValue(false)
public int getY() { return 2; }
}
static class IntExtBean {
public List<Internal> values = new ArrayList<Internal>();
public void add(int v) { values.add(new Internal(v)); }
}
static class Internal {
public int value;
public Internal(int v) { value = v; }
@JsonValue
public External asExternal() { return new External(this); }
}
static class External {
public int i;
External(Internal e) { i = e.value; }
}
// [databind#167]
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "boingo")
@JsonSubTypes(value = {@JsonSubTypes.Type(name = "boopsy", value = AdditionInterfaceImpl.class)
})
static interface AdditionInterface
{
public int add(int in);
}
public static class AdditionInterfaceImpl implements AdditionInterface
{
private final int toAdd;
@JsonCreator
public AdditionInterfaceImpl(@JsonProperty("toAdd") int toAdd) {
this.toAdd = toAdd;
}
@JsonProperty
public int getToAdd() {
return toAdd;
}
@Override
public int add(int in) {
return in + toAdd;
}
}
static class Bean838 {
@JsonValue
public String value() {
return "value";
}
}
static class Bean838Serializer extends StdScalarSerializer<Bean838>
{
public Bean838Serializer() {
super(Bean838.class);
}
@Override
public void serialize(Bean838 value, JsonGenerator gen,
SerializerProvider provider) throws IOException {
gen.writeNumber(42);
}
}
/*
/*********************************************************
/* Test cases
/*********************************************************
*/
private final ObjectMapper MAPPER = new ObjectMapper();
public void testSimpleMethodJsonValue() throws Exception
{
assertEquals("\"abc\"", MAPPER.writeValueAsString(new ValueClass<String>("abc")));
assertEquals("null", MAPPER.writeValueAsString(new ValueClass<String>(null)));
}
public void testSimpleFieldJsonValue() throws Exception
{
assertEquals("\"abc\"", MAPPER.writeValueAsString(new FieldValueClass<String>("abc")));
assertEquals("null", MAPPER.writeValueAsString(new FieldValueClass<String>(null)));
}
public void testJsonValueWithUseSerializer() throws Exception
{
String result = serializeAsString(MAPPER, new ToStringValueClass<Integer>(Integer.valueOf(123)));
assertEquals("\"123\"", result);
}
/**
* Test for verifying that additional getters won't confuse serializer.
*/
public void testMixedJsonValue() throws Exception
{
String result = serializeAsString(MAPPER, new ToStringValueClass2("xyz"));
assertEquals("\"xyz\"", result);
}
public void testDisabling() throws Exception
{
assertEquals(aposToQuotes("{'x':1,'y':2}"),
MAPPER.writeValueAsString(new DisabledJsonValue()));
}
public void testValueWithStaticType() throws Exception
{
// Ok; first, with dynamic type:
assertEquals("{\"a\":\"a\",\"b\":\"b\"}", MAPPER.writeValueAsString(new ValueWrapper()));
// then static
ObjectMapper staticMapper = new ObjectMapper();
staticMapper.configure(MapperFeature.USE_STATIC_TYPING, true);
assertEquals("{\"a\":\"a\"}", staticMapper.writeValueAsString(new ValueWrapper()));
}
public void testMapWithJsonValue() throws Exception {
// First via method
assertEquals("{\"a\":\"1\"}", MAPPER.writeValueAsString(new MapBean()));
// then field
assertEquals("{\"b\":\"2\"}", MAPPER.writeValueAsString(new MapFieldBean()));
}
public void testWithMap() throws Exception {
assertEquals("42", MAPPER.writeValueAsString(new MapAsNumber()));
}
public void testWithList() throws Exception {
assertEquals("13", MAPPER.writeValueAsString(new ListAsNumber()));
}
public void testInList() throws Exception {
IntExtBean bean = new IntExtBean();
bean.add(1);
bean.add(2);
String json = MAPPER.writeValueAsString(bean);
assertEquals(json, "{\"values\":[{\"i\":1},{\"i\":2}]}");
}
// [databind#167]
public void testPolymorphicSerdeWithDelegate() throws Exception
{
AdditionInterface adder = new AdditionInterfaceImpl(1);
assertEquals(2, adder.add(1));
String json = MAPPER.writeValueAsString(adder);
assertEquals("{\"boingo\":\"boopsy\",\"toAdd\":1}", json);
assertEquals(2, MAPPER.readValue(json, AdditionInterface.class).add(1));
}
public void testJsonValueWithCustomOverride() throws Exception
{
final Bean838 INPUT = new Bean838();
// by default, @JsonValue should be used
assertEquals(quote("value"), MAPPER.writeValueAsString(INPUT));
// but custom serializer should override it
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule()
.addSerializer(Bean838.class, new Bean838Serializer())
);
assertEquals("42", mapper.writeValueAsString(INPUT));
}
}