All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.launchdarkly.sdk.json.LDGson Maven / Gradle / Ivy

There is a newer version: 2.1.1
Show newest version
package com.launchdarkly.sdk.json;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.launchdarkly.sdk.LDUser;
import com.launchdarkly.sdk.LDValue;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

/**
 * A helper class for interoperability with application code that uses Gson.
 * 

* While the LaunchDarkly Java-based SDKs have used Gson * internally in the past, they may not always do so-- and even if they do, some SDK distributions may * embed their own copy of Gson with modified (shaded) class names so that it does not conflict with * any Gson instance elsewhere in the classpath. For both of those reasons, applications should not * assume that {@code Gson.toGson()} and {@code Gson.fromGson()}-- or any other JSON framework that is * based on reflection-- will work correctly for SDK classes, whose correct JSON representations do * not necessarily correspond to their internal field layout. This class addresses that issue * for applications that prefer to use Gson for everything rather than calling * {@link JsonSerialization} for individual objects. *

* An application that wishes to use Gson to serialize or deserialize classes from the SDK should * configure its {@code Gson} instance as follows: *


 *     import com.launchdarkly.sdk.json.LDGson;
 *     
 *     Gson gson = new GsonBuilder()
 *       .registerTypeAdapterFactory(LDGson.typeAdapters())
 *       // any other GsonBuilder options go here
 *       .create();
 * 
*

* This causes Gson to use the correct JSON representation logic (the same that would be used by * {@link JsonSerialization}) for any types that have the SDK's {@link JsonSerializable} marker * interface, such as {@link LDUser} and {@link LDValue}, regardless of whether they are the * top-level object being serialized or are contained in something else such as a collection. It * does not affect Gson's behavior for any other classes. *

* Note that some of the LaunchDarkly SDK distributions deliberately do not expose Gson as a * dependency, so if you are using Gson in your application you will need to make sure you have * defined your own dependency on it. Referencing {@link LDGson} will cause a runtime * exception if Gson is not in the caller's classpath. */ public abstract class LDGson { private static final JsonElement JSONELEMENT_TRUE = new JsonPrimitive(true); private static final JsonElement JSONELEMENT_FALSE = new JsonPrimitive(false); private LDGson() {} // Implementation note: // The reason this class exists is the Java server-side SDK's issue with Gson interoperability due // to the use of shading in the default jar artifact. If the Gson type references in this class // were also shaded in the SDK jar, then this class would not work with an unshaded Gson instance, // which would defeat the whole purpose. Therefore, the Java SDK build will need to have special- // case handling for this class (and its inner classes) when it builds the jar, and embed the // original class files instead of the ones that have had shading applied. By design, none of the // other Gson-related classes in this project would need such special handling; in the Java // server-side SDK jar, they would be meant to use the shaded copy of Gson. /** * Returns a Gson {@code TypeAdapterFactory} that defines the correct serialization and * deserialization behavior for all LaunchDarkly SDK objects that implement {@link JsonSerializable}. *


   *     import com.launchdarkly.sdk.json.LDGson;
   *     
   *     Gson gson = new GsonBuilder()
   *       .registerTypeAdapterFactory(LDGson.typeAdapters())
   *       // any other GsonBuilder options go here
   *       .create();
   * 
* @return a {@code TypeAdapterFactory} */ public static TypeAdapterFactory typeAdapters() { return LDTypeAdapterFactory.INSTANCE; } /** * Returns a Gson {@code JsonElement} that is equivalent to the specified {@link LDValue}. *

* This is slightly more efficient than using {@code Gson.toJsonTree()}. * * @param value an {@link LDValue} ({@code null} is treated as equivalent to {@link LDValue#ofNull()}) * @return a Gson {@code JsonElement} (may be a {@code JsonNull} but will never be {@code null}) */ public static JsonElement valueToJsonElement(LDValue value) { if (value == null) { return JsonNull.INSTANCE; } switch (value.getType()) { case BOOLEAN: return value.booleanValue() ? JSONELEMENT_TRUE : JSONELEMENT_FALSE; case NUMBER: return new JsonPrimitive(value.doubleValue()); case STRING: return value.stringValue() == null ? JsonNull.INSTANCE : new JsonPrimitive(value.stringValue()); case ARRAY: JsonArray a = new JsonArray(); for (LDValue e: value.values()) { a.add(valueToJsonElement(e)); } return a; case OBJECT: JsonObject o = new JsonObject(); for (String k: value.keys()) { o.add(k, valueToJsonElement(value.get(k))); } return o; default: return JsonNull.INSTANCE; } } /** * Convenience method for converting a map of {@link LDValue} values to a map of Gson {@code JsonElement}s. * * @param type of the map's keys * @param valueMap a map containing {@link LDValue} values * @return an equivalent map containing Gson {@code JsonElement} values */ public static Map valueMapToJsonElementMap(Map valueMap) { Map ret = new HashMap<>(valueMap.size()); for (Map.Entry e: valueMap.entrySet()) { ret.put(e.getKey(), valueToJsonElement(e.getValue())); } return ret; } private static class LDTypeAdapterFactory implements TypeAdapterFactory { // Note that this static initializer will only run if application code actually references LDGson. private static LDTypeAdapterFactory INSTANCE = new LDTypeAdapterFactory(); @Override public TypeAdapter create(Gson gson, TypeToken type) { if (JsonSerializable.class.isAssignableFrom(type.getRawType())) { return new LDTypeAdapter(type.getType()); } return null; } } private static class LDTypeAdapter extends TypeAdapter { private final Type objectType; LDTypeAdapter(Type objectType) { this.objectType = objectType; } @Override public void write(JsonWriter out, T value) throws IOException { if (value == null) { // COVERAGE: we don't expect this to ever happen, since Gson normally doesn't bother to call // the type adapter for any null value; it's just a sanity check. out.nullValue(); } else { JsonSerialization.serializeToGsonInternal(value, value.getClass(), new DelegatingJsonWriterAdapter(out)); } } @Override public T read(JsonReader in) throws IOException { return JsonSerialization.deserializeFromGsonInternal(new DelegatingJsonReaderAdapter(in), objectType); } } // See comments on GsonReaderAdapter for the reason this type exists. static class DelegatingJsonReaderAdapter extends GsonReaderAdapter { private final JsonReader reader; DelegatingJsonReaderAdapter(JsonReader reader) { this.reader = reader; } @Override public void beginArray() throws IOException { reader.beginArray(); } @Override public void beginObject() throws IOException { reader.beginObject(); } @Override public void endArray() throws IOException { reader.endArray(); } @Override public void endObject() throws IOException { reader.endObject(); } @Override public boolean hasNext() throws IOException { return reader.hasNext(); } @Override public boolean nextBoolean() throws IOException { return reader.nextBoolean(); } @Override public double nextDouble() throws IOException { return reader.nextDouble(); } @Override public int nextInt() throws IOException { return reader.nextInt(); } @Override public long nextLong() throws IOException { return reader.nextLong(); } @Override public String nextName() throws IOException { return reader.nextName(); } @Override public void nextNull() throws IOException { reader.nextNull(); } @Override public String nextString() throws IOException { return reader.nextString(); } @Override public void skipValue() throws IOException { reader.skipValue(); } @Override protected int peekInternal() throws IOException { return reader.peek().ordinal(); } } // See comments on GsonWriterAdapter for the reason this type exists. static class DelegatingJsonWriterAdapter extends GsonWriterAdapter { private final JsonWriter writer; DelegatingJsonWriterAdapter(JsonWriter writer) { this.writer = writer; } @Override protected void beginArrayInternal() throws IOException { writer.beginArray(); } @Override protected void beginObjectInternal() throws IOException { writer.beginObject(); } @Override protected void endArrayInternal() throws IOException { writer.endArray(); } @Override protected void endObjectInternal() throws IOException { writer.endObject(); } @Override protected void jsonValueInternal(String value) throws IOException { writer.jsonValue(value); } @Override protected void nameInternal(String name) throws IOException { writer.name(name); } @Override protected void valueInternalNull() throws IOException { writer.nullValue(); } @Override protected void valueInternalBool(boolean value) throws IOException { writer.value(value); } @Override protected void valueInternalDouble(double value) throws IOException { writer.value(value); } @Override protected void valueInternalLong(long value) throws IOException { writer.value(value); } @Override protected void valueInternalString(String value) throws IOException { writer.value(value); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy