// ================================================================================================= // Copyright 2011 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.io; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.BitSet; import com.google.common.base.Preconditions; import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; /** * A {@code Codec} that can encode and decode objects to and from JSON using the GSON library * (which in turn will use reflection). The codec uses the UTF-8 encoding. * * @author Attila Szegedi */ public class JsonCodec<T> implements Codec<T> { private static final String ENCODING = "utf-8"; private final Class<T> clazz; private final Gson gson; /** * Creates a new JSON codec instance for objects of the specified class. * * @param clazz the class of the objects the created codec is for. * @return a newly constructed JSON codec instance for objects of the requested class. */ public static <T> JsonCodec<T> create(Class<T> clazz) { return new JsonCodec<T>(clazz, DefaultGsonHolder.instance); } /** * Creates a new JSON codec instance for objects of the specified class and the specified Gson * instance. You can use this method if you need to customize the behavior of the Gson * serializer. * * @param clazz the class of the objects the created codec is for. * @param gson the Gson instance to use for serialization/deserialization. * @return a newly constructed JSON codec instance for objects of the requested class. */ public static <T> JsonCodec<T> create(Class<T> clazz, Gson gson) { return new JsonCodec<T>(clazz, gson); } private JsonCodec(Class<T> clazz, Gson gson) { Preconditions.checkNotNull(clazz); Preconditions.checkNotNull(gson); this.clazz = clazz; this.gson = gson; } private static final class DefaultGsonHolder { static final Gson instance = new Gson(); } /** * Returns a Gson exclusion strategy that excludes Thrift synthetic fields from JSON * serialization. You can pass it to a {@link GsonBuilder} to construct a customized {@link Gson} * instance to use with {@link JsonCodec#create(Class, Gson)}. * * @return a Gson exclusion strategy for thrift synthetic fields. */ public static ExclusionStrategy getThriftExclusionStrategy() { return ThriftExclusionStrategy.instance; } private static final class ThriftExclusionStrategy implements ExclusionStrategy { static final ExclusionStrategy instance = new ThriftExclusionStrategy(); public boolean shouldSkipClass(Class<?> clazz) { return false; } public boolean shouldSkipField(FieldAttributes f) { // Exclude Thrift synthetic fields return f.getDeclaredClass() == BitSet.class && f.getName().equals("__isset_bit_vector"); } } @Override public T deserialize(InputStream source) throws IOException { return gson.fromJson(new InputStreamReader(source, ENCODING), clazz); } @Override public void serialize(T item, OutputStream sink) throws IOException { final Writer w = new OutputStreamWriter(new UnflushableOutputStream(sink), ENCODING); gson.toJson(item, clazz, w); w.flush(); } private static class UnflushableOutputStream extends FilterOutputStream { UnflushableOutputStream(OutputStream out) { super(out); } @Override public void flush() throws IOException { // Intentionally do nothing } } }