/*
* Copyright 2010 Proofpoint, Inc.
*
* 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 io.airlift.json;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import org.joda.time.DateTime;
import org.joda.time.format.ISODateTimeFormat;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.google.common.base.MoreObjects.toStringHelper;
import static io.airlift.json.JsonBinder.jsonBinder;
import static org.joda.time.DateTimeZone.UTC;
import static org.testng.Assert.assertEquals;
public class TestJsonModule
{
public static final Car CAR = new Car()
.setMake("BMW")
.setModel("M3")
.setYear(2011)
.setPurchased(new DateTime().withZone(UTC))
.setNotes("sweet!")
.setNameList(superDuper("d*a*i*n"));
private ObjectMapper objectMapper;
@BeforeClass
public void setUp()
throws Exception
{
Injector injector = Guice.createInjector(new JsonModule(),
new Module()
{
@Override
public void configure(Binder binder)
{
jsonBinder(binder).addSerializerBinding(SuperDuperNameList.class).toInstance(ToStringSerializer.instance);
jsonBinder(binder).addDeserializerBinding(SuperDuperNameList.class).to(SuperDuperNameListDeserializer.class);
}
});
objectMapper = injector.getInstance(ObjectMapper.class);
}
@Test
public void testJsonCodecFactoryBinding()
throws Exception
{
Injector injector = Guice.createInjector(new JsonModule());
JsonCodecFactory codecFactory = injector.getInstance(JsonCodecFactory.class);
Person.validatePersonJsonCodec(codecFactory.jsonCodec(Person.class));
Person.validatePersonListJsonCodec(codecFactory.listJsonCodec(Person.class));
Person.validatePersonMapJsonCodec(codecFactory.mapJsonCodec(String.class, Person.class));
}
@Test
public void testSetup()
throws Exception
{
assertEquals(CAR, CAR);
String json = objectMapper.writeValueAsString(CAR);
Car actual = objectMapper.readValue(json, Car.class);
assertEquals(actual, CAR);
}
@Test
public void testFieldDetection()
throws Exception
{
Map<String, Object> actual = createCarMap();
// notes is not annotated so should not be included
// color is null so should not be included
assertEquals(actual.keySet(), ImmutableSet.of("make", "model", "year", "purchased", "nameList"));
}
@Test
public void testDateTimeRendered()
throws Exception
{
Map<String, Object> actual = createCarMap();
assertEquals(actual.get("purchased"), ISODateTimeFormat.dateTime().print(CAR.getPurchased()));
}
@Test
public void testGuavaRoundTrip()
throws Exception
{
ImmutableList<Integer> list = ImmutableList.of(3, 5, 8);
String json = objectMapper.writeValueAsString(list);
ImmutableList<Integer> actual = objectMapper.readValue(json, new TypeReference<ImmutableList<Integer>>() {});
assertEquals(actual, list);
}
@Test
public void testIgnoreUnknownFields()
throws Exception
{
Map<String, Object> data = new HashMap<>(createCarMap());
// add an unknown field
data.put("unknown", "bogus");
// Jackson should deserialize the object correctly with the extra unknown data
assertEquals(objectMapper.readValue(objectMapper.writeValueAsString(data), Car.class), CAR);
}
private Map<String, Object> createCarMap()
throws IOException
{
return objectMapper.readValue(objectMapper.writeValueAsString(CAR), new TypeReference<Object>() {});
}
public static class Car
{
// These fields are public to make sure that Jackson is ignoring them
public String make;
public String model;
public int year;
public DateTime purchased;
// property that will be null to verify that null fields are not rendered
public String color;
// non-json property to verify that auto-detection is disabled
public String notes;
// property that requires special serializer and deserializer
public SuperDuperNameList nameList;
@JsonProperty
public String getMake()
{
return make;
}
@JsonProperty
public Car setMake(String make)
{
this.make = make;
return this;
}
@JsonProperty
public String getModel()
{
return model;
}
@JsonProperty
public Car setModel(String model)
{
this.model = model;
return this;
}
@JsonProperty
public int getYear()
{
return year;
}
@JsonProperty
public Car setYear(int year)
{
this.year = year;
return this;
}
@JsonProperty
public DateTime getPurchased()
{
return purchased;
}
@JsonProperty
public Car setPurchased(DateTime purchased)
{
this.purchased = purchased;
return this;
}
@JsonProperty
public String getColor()
{
return color;
}
@JsonProperty
public Car setColor(String color)
{
this.color = color;
return this;
}
@JsonProperty
public SuperDuperNameList getNameList()
{
return nameList;
}
@JsonProperty
public Car setNameList(SuperDuperNameList nameList)
{
this.nameList = nameList;
return this;
}
// this field should not be written
public String getNotes()
{
return notes;
}
public Car setNotes(String notes)
{
this.notes = notes;
return this;
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (!(o instanceof Car)) {
return false;
}
Car car = (Car) o;
if (year != car.year) {
return false;
}
if (color != null ? !color.equals(car.color) : car.color != null) {
return false;
}
if (make != null ? !make.equals(car.make) : car.make != null) {
return false;
}
if (model != null ? !model.equals(car.model) : car.model != null) {
return false;
}
if (nameList != null ? !nameList.equals(car.nameList) : car.nameList != null) {
return false;
}
if (purchased != null ? !purchased.equals(car.purchased) : car.purchased != null) {
return false;
}
return true;
}
@Override
public int hashCode()
{
int result = make != null ? make.hashCode() : 0;
result = 31 * result + (model != null ? model.hashCode() : 0);
result = 31 * result + year;
result = 31 * result + (purchased != null ? purchased.hashCode() : 0);
result = 31 * result + (color != null ? color.hashCode() : 0);
result = 31 * result + (nameList != null ? nameList.hashCode() : 0);
return result;
}
@Override
public String toString()
{
return toStringHelper(this)
.add("make", make)
.add("model", model)
.add("year", year)
.add("purchased", purchased)
.add("color", color)
.add("notes", notes)
.add("nameList", nameList)
.toString();
}
}
public static class SuperDuperNameList
{
private List<String> name;
private SuperDuperNameList(String superDuperNameList)
{
this(superDuperNameList, null);
}
private SuperDuperNameList(String superDuperNameList, Object stopJacksonFromUsingStringConstructor)
{
this.name = ImmutableList.copyOf(Splitter.on('*').split(superDuperNameList));
}
public List<String> getName()
{
return name;
}
@Override
public String toString()
{
return Joiner.on("*").join(name);
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (!(o instanceof SuperDuperNameList)) {
return false;
}
SuperDuperNameList that = (SuperDuperNameList) o;
if (!name.equals(that.name)) {
return false;
}
return true;
}
@Override
public int hashCode()
{
return name.hashCode();
}
}
public final static class SuperDuperNameListDeserializer
extends StdScalarDeserializer<SuperDuperNameList>
{
public SuperDuperNameListDeserializer()
{
super(SuperDuperNameList.class);
}
@Override
public SuperDuperNameList deserialize(JsonParser jp, DeserializationContext context)
throws IOException
{
JsonToken token = jp.getCurrentToken();
if (token == JsonToken.VALUE_STRING) {
return new SuperDuperNameList(jp.getText(), null);
}
context.handleUnexpectedToken(handledType(), jp);
throw JsonMappingException.from(jp, null);
}
}
public static SuperDuperNameList superDuper(String superDuperNameList)
{
return new SuperDuperNameList(superDuperNameList, null);
}
}