ViewSerializationTest.java
package tools.jackson.databind.views;
import java.io.*;
import java.util.*;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.*;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.*;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.ser.std.StdSerializer;
import tools.jackson.databind.testutil.DatabindTestUtil;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for verifying JSON view functionality: ability to declaratively
* suppress subset of properties from being serialized.
*/
public class ViewSerializationTest extends DatabindTestUtil
{
// Classes that represent views
static class ViewA { }
static class ViewAA extends ViewA { }
static class ViewB { }
static class ViewBB extends ViewB { }
static class Bean
{
@JsonView(ViewA.class)
public String a = "1";
@JsonView({ViewAA.class, ViewB.class})
public String aa = "2";
@JsonView(ViewB.class)
public String getB() { return "3"; }
}
/**
* Bean with mix of explicitly annotated
* properties, and implicit ones that may or may
* not be included in views.
*/
static class MixedBean
{
@JsonView(ViewA.class)
public String a = "1";
public String getB() { return "2"; }
}
/**
* As indicated by [JACKSON-261], @JsonView should imply
* that associated element (method, field) is to be considered
* a property
*/
static class ImplicitBean {
@JsonView(ViewA.class)
private int a = 1;
}
static class VisibilityBean {
@JsonProperty protected String id = "id";
@JsonView(ViewA.class)
public String value = "x";
}
public static class WebView { }
public static class OtherView { }
public static class Foo {
@JsonView(WebView.class)
public int getFoo() { return 3; }
}
// [databind#5937]
static class Bean5937 { }
static class Views
{
public interface View { }
public interface ExtendedView extends View { }
}
static class ComplexTestData
{
String nameNull = null;
String nameComplex = "complexValue";
String nameComplexHidden = "nameComplexHiddenValue";
SimpleTestData testData = new SimpleTestData();
SimpleTestData[] testDataArray = new SimpleTestData[] { new SimpleTestData(), null };
@JsonView( Views.View.class )
public String getNameNull() { return nameNull; }
public void setNameNull( String nameNull ) { this.nameNull = nameNull; }
@JsonView( Views.View.class )
public String getNameComplex() { return nameComplex; }
public void setNameComplex( String nameComplex ) { this.nameComplex = nameComplex; }
public String getNameComplexHidden() { return nameComplexHidden; }
public void setNameComplexHidden( String nameComplexHidden ) { this.nameComplexHidden = nameComplexHidden; }
@JsonView( Views.View.class )
public SimpleTestData getTestData() { return testData; }
public void setTestData( SimpleTestData testData ) { this.testData = testData; }
@JsonView( Views.View.class )
public SimpleTestData[] getTestDataArray() { return testDataArray; }
public void setTestDataArray( SimpleTestData[] testDataArray ) { this.testDataArray = testDataArray; }
}
static class SimpleTestData
{
String name = "shown";
String nameHidden = "hidden";
@JsonView( Views.View.class )
public String getName() { return name; }
public void setName( String name ) { this.name = name; }
public String getNameHidden( ) { return nameHidden; }
public void setNameHidden( String nameHidden ) { this.nameHidden = nameHidden; }
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
// Ensure `MapperFeature.DEFAULT_VIEW_INCLUSION` is enabled
// (its default differs b/w Jackson 2.x and 3.x)
private final ObjectMapper MAPPER = jsonMapperBuilder()
.enable(MapperFeature.DEFAULT_VIEW_INCLUSION)
.build();
@SuppressWarnings("unchecked")
@Test
public void simple() throws Exception
{
StringWriter sw = new StringWriter();
// Ok, first, using no view whatsoever; all 3
Bean bean = new Bean();
Map<String,Object> map = writeAndMap(MAPPER, bean);
assertEquals(3, map.size());
// Then with "ViewA", just one property
sw = new StringWriter();
MAPPER.writerWithView(ViewA.class).writeValue(sw, bean);
map = MAPPER.readValue(sw.toString(), Map.class);
assertEquals(1, map.size());
assertEquals("1", map.get("a"));
// "ViewAA", 2 properties
sw = new StringWriter();
MAPPER.writerWithView(ViewAA.class).writeValue(sw, bean);
map = MAPPER.readValue(sw.toString(), Map.class);
assertEquals(2, map.size());
assertEquals("1", map.get("a"));
assertEquals("2", map.get("aa"));
// "ViewB", 2 prop2
String json = MAPPER.writerWithView(ViewB.class).writeValueAsString(bean);
map = MAPPER.readValue(json, Map.class);
assertEquals(2, map.size());
assertEquals("2", map.get("aa"));
assertEquals("3", map.get("b"));
// and "ViewBB", 2 as well
json = MAPPER.writerWithView(ViewBB.class).writeValueAsString(bean);
map = MAPPER.readValue(json, Map.class);
assertEquals(2, map.size());
assertEquals("2", map.get("aa"));
assertEquals("3", map.get("b"));
// and finally, without view.
json = MAPPER.writerWithView(null).writeValueAsString(bean);
map = MAPPER.readValue(json, Map.class);
assertEquals(3, map.size());
}
/**
* Unit test to verify implementation of [JACKSON-232], to
* allow "opt-in" handling for JSON Views: that is, that
* default for properties is to exclude unless included in
* a view.
*/
@SuppressWarnings("unchecked")
@Test
public void defaultExclusion() throws Exception
{
MixedBean bean = new MixedBean();
// default setting: both fields will get included
String json = MAPPER.writerWithView(ViewA.class).writeValueAsString(bean);
Map<String,Object> map = MAPPER.readValue(json, Map.class);
assertEquals(2, map.size());
assertEquals("1", map.get("a"));
assertEquals("2", map.get("b"));
// but can also change (but not necessarily on the fly...)
ObjectMapper mapper = jsonMapperBuilder()
.disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
.build();
// with this setting, only explicit inclusions count:
json = mapper.writerWithView(ViewA.class).writeValueAsString(bean);
map = mapper.readValue(json, Map.class);
assertEquals(1, map.size());
assertEquals("1", map.get("a"));
assertNull(map.get("b"));
// but without view, view processing disabled:
json = mapper.writer().withView(null).writeValueAsString(bean);
map = mapper.readValue(json, Map.class);
assertEquals(2, map.size());
assertEquals("1", map.get("a"));
assertEquals("2", map.get("b"));
}
/**
* As per [JACKSON-261], @JsonView annotation should imply that associated
* method/field does indicate a property.
*/
@Test
public void implicitAutoDetection() throws Exception
{
assertEquals("{\"a\":1}",
MAPPER.writeValueAsString(new ImplicitBean()));
}
@Test
public void visibility() throws Exception
{
VisibilityBean bean = new VisibilityBean();
// Without view setting, should only see "id"
String json = MAPPER.writerWithView(Object.class).writeValueAsString(bean);
//json = mapper.writeValueAsString(bean);
assertEquals("{\"id\":\"id\"}", json);
}
// [JACKSON-868]
@Test
public void issue868() throws Exception
{
ObjectMapper mapper = jsonMapperBuilder()
.changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_DEFAULT))
.build();
assertEquals("{}",
mapper.writerWithView(OtherView.class).writeValueAsString(new Foo()));
}
// [databind#5937]
@Test
public void withActiveView() throws Exception
{
final Class<?>[] insideView = new Class<?>[1];
final Class<?>[] afterView = new Class<?>[1];
ObjectMapper mapper = jsonMapperBuilder()
.addModule(new SimpleModule()
.addSerializer(Bean5937.class, new StdSerializer<Bean5937>(Bean5937.class) {
@Override
public void serialize(Bean5937 value, JsonGenerator g,
SerializationContext ctxt) {
ctxt.withActiveView(ViewA.class, () -> {
insideView[0] = ctxt.getActiveView();
});
afterView[0] = ctxt.getActiveView();
g.writeStartObject();
g.writeEndObject();
}
}))
.build();
// No initial view: inside == ViewA, after reverts to null
mapper.writeValueAsString(new Bean5937());
assertSame(ViewA.class, insideView[0]);
assertNull(afterView[0]);
// With initial view ViewB: inside == ViewA, after reverts to ViewB
mapper.writerWithView(ViewB.class).writeValueAsString(new Bean5937());
assertSame(ViewA.class, insideView[0]);
assertSame(ViewB.class, afterView[0]);
}
// [databind#5937]: active view must be reverted even if callback throws
@Test
public void withActiveViewRevertsOnThrow() throws Exception
{
final Class<?>[] afterView = new Class<?>[1];
final RuntimeException boom = new RuntimeException("boom");
ObjectMapper mapper = jsonMapperBuilder()
.addModule(new SimpleModule()
.addSerializer(Bean5937.class, new StdSerializer<Bean5937>(Bean5937.class) {
@Override
public void serialize(Bean5937 value, JsonGenerator g,
SerializationContext ctxt) {
try {
ctxt.withActiveView(ViewA.class, () -> { throw boom; });
} catch (RuntimeException e) {
if (e != boom) throw e;
}
afterView[0] = ctxt.getActiveView();
g.writeStartObject();
g.writeEndObject();
}
}))
.build();
mapper.writerWithView(ViewB.class).writeValueAsString(new Bean5937());
assertSame(ViewB.class, afterView[0]);
}
// [databind#5937]: nested withActiveView calls must each revert to the
// view in effect at their entry
@Test
public void withActiveViewNested() throws Exception
{
final Class<?>[] innerView = new Class<?>[1];
final Class<?>[] betweenView = new Class<?>[1];
final Class<?>[] afterView = new Class<?>[1];
ObjectMapper mapper = jsonMapperBuilder()
.addModule(new SimpleModule()
.addSerializer(Bean5937.class, new StdSerializer<Bean5937>(Bean5937.class) {
@Override
public void serialize(Bean5937 value, JsonGenerator g,
SerializationContext ctxt) {
ctxt.withActiveView(ViewA.class, () -> {
ctxt.withActiveView(ViewAA.class, () -> {
innerView[0] = ctxt.getActiveView();
});
betweenView[0] = ctxt.getActiveView();
});
afterView[0] = ctxt.getActiveView();
g.writeStartObject();
g.writeEndObject();
}
}))
.build();
mapper.writerWithView(ViewB.class).writeValueAsString(new Bean5937());
assertSame(ViewAA.class, innerView[0]);
assertSame(ViewA.class, betweenView[0]);
assertSame(ViewB.class, afterView[0]);
}
// Nested/array data binding with a view: only view-annotated properties serialized
@Test
public void dataBindingUsage() throws Exception
{
ObjectMapper mapper = createNonNullMapper();
String result = mapper.writerWithView(Views.View.class).writeValueAsString(new ComplexTestData());
assertEquals(-1, result.indexOf( "nameHidden" ));
}
@Test
public void dataBindingUsageWithoutView() throws Exception
{
ObjectMapper mapper = createNonNullMapper();
String json = mapper.writerWithView(null).writeValueAsString(new ComplexTestData());
assertTrue(json.indexOf( "nameHidden" ) > 0);
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
private ObjectMapper createNonNullMapper()
{
return jsonMapperBuilder()
.changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL))
.disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.build();
}
}