/*
* 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.annotations;
import java.util.LinkedHashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
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.JsonValue;
import com.github.nmorel.gwtjackson.client.exception.JsonDeserializationException;
import com.github.nmorel.gwtjackson.shared.AbstractTester;
import com.github.nmorel.gwtjackson.shared.ObjectMapperTester;
import com.github.nmorel.gwtjackson.shared.ObjectReaderTester;
import com.github.nmorel.gwtjackson.shared.ObjectWriterTester;
/**
* @author Nicolas Morel
*/
public final class JsonCreatorTester extends AbstractTester {
public static class BeanWithDefaultConstructorPrivate {
public int intProperty;
public String stringProperty;
public Boolean booleanProperty;
private BeanWithDefaultConstructorPrivate() {
}
}
public static class BeanWithoutDefaultConstructorAndNoAnnotation {
private int intProperty;
private String stringProperty;
private Boolean booleanProperty;
public BeanWithoutDefaultConstructorAndNoAnnotation( int intProperty, String stringProperty ) {
this.intProperty = intProperty;
this.stringProperty = stringProperty;
}
public int getIntProperty() {
return intProperty;
}
public String getStringProperty() {
return stringProperty;
}
public Boolean getBooleanProperty() {
return booleanProperty;
}
public void setBooleanProperty( Boolean booleanProperty ) {
this.booleanProperty = booleanProperty;
}
}
public static class BeanWithoutDefaultConstructorAndPropertiesAnnotation {
@JsonProperty( "@intProperty" )
private int intProperty;
@JsonProperty( value = "stringProperty!", required = true )
private String stringProperty;
private Boolean booleanProperty;
public BeanWithoutDefaultConstructorAndPropertiesAnnotation( @JsonProperty( "@intProperty" ) int intProperty, @JsonProperty(
value = "stringProperty!", required = true ) String stringProperty ) {
this.intProperty = intProperty;
this.stringProperty = stringProperty;
}
public int getIntProperty() {
return intProperty;
}
public String getStringProperty() {
return stringProperty;
}
public Boolean getBooleanProperty() {
return booleanProperty;
}
public void setBooleanProperty( Boolean booleanProperty ) {
this.booleanProperty = booleanProperty;
}
}
public static class BeanWithConstructorAnnotated {
private int intProperty;
private String stringProperty;
private Boolean booleanProperty;
@JsonCreator
public BeanWithConstructorAnnotated( @JsonProperty( "intProperty" ) int intProperty, @JsonProperty( "stringProperty" ) String
stringProperty ) {
this.intProperty = intProperty;
this.stringProperty = stringProperty;
}
public int getIntProperty() {
return intProperty;
}
public String getStringProperty() {
return stringProperty;
}
public Boolean getBooleanProperty() {
return booleanProperty;
}
public void setBooleanProperty( Boolean booleanProperty ) {
this.booleanProperty = booleanProperty;
}
}
@JsonPropertyOrder( alphabetic = true )
public static class BeanWithFactoryMethod {
@JsonCreator
static BeanWithFactoryMethod newInstance( @JsonProperty( "stringProperty" ) String stringProperty, @JsonProperty( "intProperty" )
int intProperty ) {
return new BeanWithFactoryMethod( intProperty, stringProperty );
}
private int intProperty;
private String stringProperty;
private Boolean booleanProperty;
private BeanWithFactoryMethod( int intProperty, String stringProperty ) {
this.intProperty = intProperty;
this.stringProperty = stringProperty;
}
public int getIntProperty() {
return intProperty;
}
public String getStringProperty() {
return stringProperty;
}
public Boolean getBooleanProperty() {
return booleanProperty;
}
public void setBooleanProperty( Boolean booleanProperty ) {
this.booleanProperty = booleanProperty;
}
}
@JsonPropertyOrder( value = {"booleanProperty", "intProperty", "stringProperty"} )
public static class BeanWithPrivateFactoryMethod {
@JsonCreator
private static BeanWithPrivateFactoryMethod newInstance( @JsonProperty( "stringProperty" ) String stringProperty, @JsonProperty(
"intProperty" ) int intProperty ) {
return new BeanWithPrivateFactoryMethod( intProperty, stringProperty );
}
private int intProperty;
private String stringProperty;
private Boolean booleanProperty;
private BeanWithPrivateFactoryMethod( int intProperty, String stringProperty ) {
this.intProperty = intProperty;
this.stringProperty = stringProperty;
}
public int getIntProperty() {
return intProperty;
}
public String getStringProperty() {
return stringProperty;
}
public Boolean getBooleanProperty() {
return booleanProperty;
}
public void setBooleanProperty( Boolean booleanProperty ) {
this.booleanProperty = booleanProperty;
}
}
public static class BeanWithPropertiesOnlyPresentOnConstructor {
private int result;
@JsonCreator
public BeanWithPropertiesOnlyPresentOnConstructor( @JsonProperty( "x" ) int x, @JsonProperty( "y" ) int y ) {
this.result = x * y;
}
public int getResult() {
return result;
}
}
public static class BeanWithBooleanFactoryDelegation {
@JsonCreator
public static BeanWithBooleanFactoryDelegation create( Boolean value ) {
if ( null == value ) {
return null;
}
return new BeanWithBooleanFactoryDelegation( !value );
}
private final Boolean value;
@JsonIgnore // we force jackson to ignore this constructor or else it uses this one instead of the factory method
private BeanWithBooleanFactoryDelegation( Boolean v ) { value = v; }
@JsonValue
public Boolean getValue() {
return value;
}
}
public static class BeanWithBooleanConstructorDelegation {
private final Boolean value;
@JsonCreator
public BeanWithBooleanConstructorDelegation( Boolean v ) { value = v; }
@JsonValue
public Boolean getValue() {
return value;
}
}
@JsonTypeInfo( use = Id.CLASS, include = As.PROPERTY )
public static class BeanWithBooleanConstructorDelegationAndTypeInfo {
private final Boolean value;
@JsonCreator
public BeanWithBooleanConstructorDelegationAndTypeInfo( Boolean v ) { value = v; }
@JsonValue
public Boolean getValue() {
return value;
}
}
public static class BeanWithObjectConstructorDelegation {
private final String a;
private Integer b;
@JsonCreator
public BeanWithObjectConstructorDelegation( Object obj ) {
Map map = (Map) obj;
a = (String) map.get( "propertyA" );
b = (Integer) map.get( "propertyB" );
}
public Integer getB() {
return b;
}
public void setB( Integer b ) {
this.b = b;
}
}
@JsonTypeInfo( use = Id.CLASS, include = As.WRAPPER_ARRAY )
public static class BeanWithMapConstructorDelegationAndTypeInfo {
private final String a;
private Integer b;
@JsonCreator
public BeanWithMapConstructorDelegationAndTypeInfo( Map map ) {
a = (String) map.get( "propertyA" );
b = (Integer) map.get( "propertyB" );
}
@JsonValue
public Map get() {
Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put( "propertyA", a );
map.put( "propertyB", b );
return map;
}
}
public static abstract class AbstractBeanWithFactoryMethod {
private final String a;
private Integer b;
@JsonCreator
public static AbstractBeanWithFactoryMethod create( @JsonProperty( "a" ) String a ) {
return new AbstractBeanWithFactoryMethodImpl( a );
}
protected AbstractBeanWithFactoryMethod( String a ) {
this.a = a;
}
public String getA() {
return a;
}
public Integer getB() {
return b;
}
public void setB( Integer b ) {
this.b = b;
}
public abstract String getAB();
}
public static class AbstractBeanWithFactoryMethodImpl extends AbstractBeanWithFactoryMethod {
public AbstractBeanWithFactoryMethodImpl( String a ) {
super( a );
}
@Override
public String getAB() {
return getA() + "+" + getB();
}
}
public static final JsonCreatorTester INSTANCE = new JsonCreatorTester();
private JsonCreatorTester() {
}
public void testSerializeBeanWithDefaultConstructorPrivate( ObjectWriterTester<BeanWithDefaultConstructorPrivate> writer ) {
BeanWithDefaultConstructorPrivate bean = new BeanWithDefaultConstructorPrivate();
bean.intProperty = 15;
bean.stringProperty = "IAmAString";
bean.booleanProperty = true;
String expected = "{\"intProperty\":15," +
"\"stringProperty\":\"IAmAString\"," +
"\"booleanProperty\":true}";
String result = writer.write( bean );
assertEquals( expected, result );
}
public void testDeserializeBeanWithDefaultConstructorPrivate( ObjectReaderTester<BeanWithDefaultConstructorPrivate> reader ) {
String input = "{\"intProperty\":15," +
"\"stringProperty\":\"IAmAString\"," +
"\"booleanProperty\":true}";
BeanWithDefaultConstructorPrivate result = reader.read( input );
assertEquals( 15, result.intProperty );
assertEquals( "IAmAString", result.stringProperty );
assertTrue( result.booleanProperty );
}
public void testSerializeBeanWithoutDefaultConstructorAndNoAnnotation(
ObjectWriterTester<BeanWithoutDefaultConstructorAndNoAnnotation> writer ) {
BeanWithoutDefaultConstructorAndNoAnnotation bean = new BeanWithoutDefaultConstructorAndNoAnnotation( 15, "IAmAString" );
bean.booleanProperty = true;
String expected = "{\"intProperty\":15," +
"\"stringProperty\":\"IAmAString\"," +
"\"booleanProperty\":true}";
String result = writer.write( bean );
assertEquals( expected, result );
}
public void testDeserializeBeanWithoutDefaultConstructorAndNoAnnotation(
ObjectReaderTester<BeanWithoutDefaultConstructorAndNoAnnotation> reader ) {
String input = "{\"intProperty\":15," +
"\"stringProperty\":\"IAmAString\"," +
"\"booleanProperty\":true}";
try {
reader.read( input );
fail();
} catch ( JsonDeserializationException e ) {
// no way to instantiate it
}
}
public void testSerializeBeanWithoutDefaultConstructorAndPropertiesAnnotation(
ObjectWriterTester<BeanWithoutDefaultConstructorAndPropertiesAnnotation> writer ) {
BeanWithoutDefaultConstructorAndPropertiesAnnotation bean = new BeanWithoutDefaultConstructorAndPropertiesAnnotation( 15,
"IAmAString" );
bean.booleanProperty = true;
String expected = "{\"@intProperty\":15," +
"\"stringProperty!\":\"IAmAString\"," +
"\"booleanProperty\":true}";
String result = writer.write( bean );
assertEquals( expected, result );
}
public void testDeserializeBeanWithoutDefaultConstructorAndPropertiesAnnotation(
ObjectReaderTester<BeanWithoutDefaultConstructorAndPropertiesAnnotation> reader ) {
String input = "{\"@intProperty\":15," +
"\"stringProperty!\":\"IAmAString\"," +
"\"booleanProperty\":true}";
BeanWithoutDefaultConstructorAndPropertiesAnnotation result = reader.read( input );
assertEquals( 15, result.intProperty );
assertEquals( "IAmAString", result.stringProperty );
assertTrue( result.booleanProperty );
}
public void testDeserializeBeanWithMissingRequiredPropertyInCreator(
ObjectReaderTester<BeanWithoutDefaultConstructorAndPropertiesAnnotation> reader ) {
String input = "{\"@intProperty\":15,\"booleanProperty\":true}";
try {
reader.read( input );
fail( "Expected an exception because a required property is missing" );
} catch ( JsonDeserializationException e ) {
}
}
public void testSerializeBeanWithConstructorAnnotated( ObjectWriterTester<BeanWithConstructorAnnotated> writer ) {
BeanWithConstructorAnnotated bean = new BeanWithConstructorAnnotated( 15, "IAmAString" );
bean.booleanProperty = true;
String expected = "{\"intProperty\":15," +
"\"stringProperty\":\"IAmAString\"," +
"\"booleanProperty\":true}";
String result = writer.write( bean );
assertEquals( expected, result );
}
public void testDeserializeBeanWithConstructorAnnotated( ObjectReaderTester<BeanWithConstructorAnnotated> reader ) {
String input = "{\"intProperty\":15," +
"\"stringProperty\":\"IAmAString\"," +
"\"booleanProperty\":true}";
BeanWithConstructorAnnotated result = reader.read( input );
assertEquals( 15, result.intProperty );
assertEquals( "IAmAString", result.stringProperty );
assertTrue( result.booleanProperty );
// test with booleanProperty at 1st position and null
input = "{\"booleanProperty\":null," +
"\"intProperty\":16," +
"\"stringProperty\":\"IAmANewString\"}";
result = reader.read( input );
assertEquals( 16, result.intProperty );
assertEquals( "IAmANewString", result.stringProperty );
assertNull( result.booleanProperty );
}
public void testSerializeBeanWithFactoryMethod( ObjectWriterTester<BeanWithFactoryMethod> writer ) {
BeanWithFactoryMethod bean = new BeanWithFactoryMethod( 15, "IAmAString" );
bean.booleanProperty = true;
// There is an alphabetic order asked but the order of properties in factory method is stronger
String expected = "{\"intProperty\":15," +
"\"stringProperty\":\"IAmAString\"," +
"\"booleanProperty\":true}";
String result = writer.write( bean );
assertEquals( expected, result );
}
public void testDeserializeBeanWithFactoryMethod( ObjectReaderTester<BeanWithFactoryMethod> reader ) {
String input = "{\"intProperty\":15," +
"\"stringProperty\":\"IAmAString\"," +
"\"booleanProperty\":true}";
BeanWithFactoryMethod result = reader.read( input );
assertEquals( 15, result.intProperty );
assertEquals( "IAmAString", result.stringProperty );
assertTrue( result.booleanProperty );
}
public void testSerializeBeanWithPrivateFactoryMethod( ObjectWriterTester<BeanWithPrivateFactoryMethod> writer ) {
BeanWithPrivateFactoryMethod bean = new BeanWithPrivateFactoryMethod( 15, "IAmAString" );
bean.booleanProperty = true;
// There is an explicit order on type. The order of parameters of factory method is overrided.
String expected = "{\"booleanProperty\":true," +
"\"intProperty\":15," +
"\"stringProperty\":\"IAmAString\"}";
String result = writer.write( bean );
assertEquals( expected, result );
}
public void testDeserializeBeanWithPrivateFactoryMethod( ObjectReaderTester<BeanWithPrivateFactoryMethod> reader ) {
String input = "{\"intProperty\":15," +
"\"stringProperty\":\"IAmAString\"," +
"\"booleanProperty\":true}";
BeanWithPrivateFactoryMethod result = reader.read( input );
assertEquals( 15, result.intProperty );
assertEquals( "IAmAString", result.stringProperty );
assertTrue( result.booleanProperty );
}
public void testSerializeBeanWithPropertiesOnlyPresentOnConstructor( ObjectWriterTester<BeanWithPropertiesOnlyPresentOnConstructor>
writer ) {
BeanWithPropertiesOnlyPresentOnConstructor bean = new BeanWithPropertiesOnlyPresentOnConstructor( 15, 10 );
String expected = "{\"result\":150}";
String result = writer.write( bean );
assertEquals( expected, result );
}
public void testDeserializeBeanWithPropertiesOnlyPresentOnConstructor( ObjectReaderTester<BeanWithPropertiesOnlyPresentOnConstructor>
reader ) {
String input = "{\"x\":15,\"y\":10}";
BeanWithPropertiesOnlyPresentOnConstructor result = reader.read( input );
assertEquals( 150, result.result );
}
public void testDeserializeBeanWithBooleanFactoryDelegation( ObjectReaderTester<BeanWithBooleanFactoryDelegation> reader ) {
BeanWithBooleanFactoryDelegation result = reader.read( "true" );
assertFalse( result.value );
result = reader.read( "false" );
assertTrue( result.value );
}
public void testDeserializeBeanWithBooleanConstructorDelegation( ObjectReaderTester<BeanWithBooleanConstructorDelegation> reader ) {
BeanWithBooleanConstructorDelegation result = reader.read( "true" );
assertTrue( result.value );
result = reader.read( "false" );
assertFalse( result.value );
}
public void testBeanWithBooleanConstructorDelegationAndTypeInfo( ObjectMapperTester<BeanWithBooleanConstructorDelegationAndTypeInfo>
mapper ) {
BeanWithBooleanConstructorDelegationAndTypeInfo bean = new BeanWithBooleanConstructorDelegationAndTypeInfo( true );
String json = mapper.write( bean );
assertEquals( "[\"" + BeanWithBooleanConstructorDelegationAndTypeInfo.class.getName() + "\",true]", json );
bean = mapper.read( json );
assertTrue( bean.value );
}
public void testDeserializeBeanWithObjectConstructorDelegation( ObjectReaderTester<BeanWithObjectConstructorDelegation> reader ) {
BeanWithObjectConstructorDelegation result = reader.read( "{\"propertyA\":\"string A\",\"propertyB\":148}" );
assertEquals( "string A", result.a );
assertEquals( 148, result.b.intValue() );
}
public void testBeanWithMapConstructorDelegationAndTypeInfo( ObjectMapperTester<BeanWithMapConstructorDelegationAndTypeInfo> mapper ) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put( "propertyA", "string A" );
map.put( "propertyB", 148 );
BeanWithMapConstructorDelegationAndTypeInfo bean = new BeanWithMapConstructorDelegationAndTypeInfo( map );
String json = mapper.write( bean );
assertEquals( "[\"" + BeanWithMapConstructorDelegationAndTypeInfo.class.getName() + "\",{\"propertyA\":\"string A\"," +
"\"propertyB\":148}]", json );
bean = mapper.read( json );
assertEquals( "string A", bean.a );
assertEquals( 148, bean.b.intValue() );
}
public void testDeserializeAbstractBeanWithFactoryMethod( ObjectReaderTester<AbstractBeanWithFactoryMethod> reader ) {
AbstractBeanWithFactoryMethod result = reader.read( "{\"a\":\"string A\",\"b\":148}" );
assertEquals( "string A", result.getA() );
assertEquals( 148, result.getB().intValue());
assertEquals( "string A+148", result.getAB() );
}
}