/* * Copyright 2013 Nicolas Morel * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.nmorel.gwtjackson.shared.advanced.jsontype; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeId; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; import com.fasterxml.jackson.annotation.JsonTypeName; import com.github.nmorel.gwtjackson.client.exception.JsonSerializationException; import com.github.nmorel.gwtjackson.shared.AbstractTester; import com.github.nmorel.gwtjackson.shared.ObjectMapperTester; import com.github.nmorel.gwtjackson.shared.ObjectWriterTester; /** * Tests to verify [JACKSON-437], [JACKSON-762] */ public final class VisibleTypeIdTester extends AbstractTester { // type id as property, exposed @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true ) @JsonTypeName( "BaseType" ) public static class PropertyBean { private final int a; protected String type; @JsonCreator public PropertyBean( @JsonProperty( value = "a", required = true ) int a ) { this.a = a; } public int getA() { return a; } public void setType( String t ) { type = t; } } // as wrapper-array @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_ARRAY, property = "type", visible = true ) @JsonTypeName( "ArrayType" ) public static class WrapperArrayBean { public int a = 1; protected String type; public void setType( String t ) { type = t; } } // as wrapper-object @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT, property = "type", visible = true ) @JsonTypeName( "ObjectType" ) public static class WrapperObjectBean { public int a = 2; protected String type; public void setType( String t ) { type = t; } } // // // [JACKSON-762]: type id from property @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type" ) public static class TypeIdFromFieldProperty { public int a = 3; @JsonTypeId public String type = "SomeType"; } @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_ARRAY, property = "type" ) public static class TypeIdFromFieldArray { public int a = 3; @JsonTypeId public String type = "SomeType"; } @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT, property = "type" ) public static class TypeIdFromMethodObject { public int a = 3; @JsonTypeId public String getType() { return "SomeType"; } } public static class ExternalIdWrapper2 { @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type", visible = true ) public ExternalIdBean2 bean = new ExternalIdBean2(); } public static class ExternalIdBean2 { public int a = 2; /* Type id property itself can not be external, as it is conceptually * part of the bean for which info is written: */ @JsonTypeId public String getType() { return "SomeType"; } } // Invalid definition: multiple type ids public static class MultipleIds { @JsonTypeId public String type1 = "type1"; @JsonTypeId public String getType2() { return "type2"; } } @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, property = "name" ) @JsonSubTypes( {@JsonSubTypes.Type( value = I263Impl.class )} ) public static abstract class I263Base { @JsonTypeId public abstract String getName(); } @JsonPropertyOrder( {"age", "name"} ) @JsonTypeName( "bob" ) public static class I263Impl extends I263Base { public int age = 41; @Override public String getName() { return "bob"; } } // [databind#408] public static class ExternalBeanWithId { protected String _type; @JsonTypeInfo( use = Id.NAME, include = As.EXTERNAL_PROPERTY, property = "type", visible = true ) public ValueBean bean; public ExternalBeanWithId() { } public ExternalBeanWithId( int v ) { bean = new ValueBean( v ); } public void setType( String t ) { _type = t; } } @JsonTypeName( "vbean" ) static class ValueBean { public int value; public ValueBean() { } public ValueBean( int v ) { value = v; } } public static final VisibleTypeIdTester INSTANCE = new VisibleTypeIdTester(); private VisibleTypeIdTester() { } /* /********************************************************** /* Unit tests, success /********************************************************** */ public void testVisibleWithProperty( ObjectMapperTester<PropertyBean> mapper ) { String json = mapper.write( new PropertyBean( 3 ) ); // just default behavior: assertEquals( "{\"type\":\"BaseType\",\"a\":3}", json ); // but then expect to read it back PropertyBean result = mapper.read( json ); assertEquals( "BaseType", result.type ); // also, should work with order reversed result = mapper.read( "{\"a\":7, \"type\":\"BaseType\"}" ); assertEquals( 7, result.a ); assertEquals( "BaseType", result.type ); } public void testVisibleWithWrapperArray( ObjectMapperTester<WrapperArrayBean> mapper ) { String json = mapper.write( new WrapperArrayBean() ); // just default behavior: assertEquals( "[\"ArrayType\",{\"a\":1}]", json ); // but then expect to read it back WrapperArrayBean result = mapper.read( json ); assertEquals( "ArrayType", result.type ); assertEquals( 1, result.a ); } public void testVisibleWithWrapperObject( ObjectMapperTester<WrapperObjectBean> mapper ) { String json = mapper.write( new WrapperObjectBean() ); assertEquals( "{\"ObjectType\":{\"a\":2}}", json ); // but then expect to read it back WrapperObjectBean result = mapper.read( json ); assertEquals( "ObjectType", result.type ); } // [JACKSON-762] public void testTypeIdFromProperty( ObjectWriterTester<TypeIdFromFieldProperty> writer ) { assertEquals( "{\"type\":\"SomeType\",\"a\":3}", writer.write( new TypeIdFromFieldProperty() ) ); } public void testTypeIdFromArray( ObjectWriterTester<TypeIdFromFieldArray> writer ) { assertEquals( "[\"SomeType\",{\"a\":3}]", writer.write( new TypeIdFromFieldArray() ) ); } public void testTypeIdFromObject( ObjectWriterTester<TypeIdFromMethodObject> writer ) { assertEquals( "{\"SomeType\":{\"a\":3}}", writer.write( new TypeIdFromMethodObject() ) ); } public void testTypeIdFromExternal( ObjectWriterTester<ExternalIdWrapper2> writer ) { String json = writer.write( new ExternalIdWrapper2() ); // Implementation detail: type id written AFTER value, due to constraints assertEquals( "{\"bean\":{\"a\":2},\"type\":\"SomeType\"}", json ); } public void testIssue263( ObjectMapperTester<I263Base> mapper ) { // first, serialize: assertEquals( "{\"name\":\"bob\",\"age\":41}", mapper.write( new I263Impl() ) ); // then bring back: I263Base result = mapper.read( "{\"age\":19,\"name\":\"bob\"}" ); assertTrue( result instanceof I263Impl ); assertEquals( 19, ((I263Impl) result).age ); } // [databind#408] /* NOTE: Handling changed between 2.4 and 2.5; earlier, type id was 'injected' * inside POJO; but with 2.5 this was fixed so it would remain outside, similar * to how JSON structure is. */ public void testVisibleTypeId408( ObjectMapperTester<ExternalBeanWithId> mapper ) { String json = mapper.write( new ExternalBeanWithId( 3 ) ); ExternalBeanWithId result = mapper.read( json ); assertNotNull( result ); assertNotNull( result.bean ); assertEquals( 3, result.bean.value ); assertEquals( "vbean", result._type ); } /* /********************************************************** /* Unit tests, fails /********************************************************** */ public void testInvalidMultipleTypeIds( ObjectMapperTester<MultipleIds> writer ) { try { writer.write( new MultipleIds() ); fail( "Should have failed" ); } catch ( JsonSerializationException e ) { // expected } } }