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

com.launchdarkly.sdk.json.JsonSerialization 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.GsonBuilder;
import com.launchdarkly.sdk.EvaluationDetail;
import com.launchdarkly.sdk.EvaluationReason;
import com.launchdarkly.sdk.LDUser;
import com.launchdarkly.sdk.LDValue;
import com.launchdarkly.sdk.UserAttribute;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * Helper methods for JSON serialization of SDK classes.
 * 

* 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. Instead, they should always use one of * the following: *

    *
  1. The {@link JsonSerialization} methods. *
  2. A Gson instance that has been configured with {@link LDGson}. *
  3. For {@link LDValue}, you may also use the convenience methods {@link LDValue#toJsonString()} and * {@link LDValue#parse(String)}. *
*/ public abstract class JsonSerialization { private JsonSerialization() {} static final List> knownDeserializableClasses = new ArrayList<>(); // This Gson instance has serializeNulls enabled because we want the decision of whether to include // a null property value to be left up to our own serializers. The default behavior would mean that // the GsonWriter would not allow us to write a null property value ever. private static final Gson gson = new GsonBuilder().serializeNulls().create(); /** * Converts an object to its JSON representation. *

* This is only usable for classes that have the {@link JsonSerializable} marker interface, * indicating that the SDK knows how to serialize them. * * @param class of the object being serialized * @param instance the instance to serialize * @return the object's JSON encoding as a string */ public static String serialize(T instance) { return serializeInternal(instance); } // We use this internally in situations where generic type checking isn't desirable static String serializeInternal(Object instance) { return gson.toJson(instance); } /** * Parses an object from its JSON representation. *

* This is only usable for classes that have the {@link JsonSerializable} marker interface, * indicating that the SDK knows how to serialize them. *

* The current implementation is limited in its ability to handle generic types. Currently, the only * such type defined by the SDKs is {@link com.launchdarkly.sdk.EvaluationDetail}. You can serialize * any {@code EvaluationDetail} instance and it will represent the {@code T} value correctly, but * when deserializing, you will always get {@code EvaluationDetail}. * * @param class of the object being deserialized * @param json the object's JSON encoding as a string * @param objectClass class of the object being deserialized * @return the deserialized instance * @throws SerializationException if the JSON encoding was invalid */ public static T deserialize(String json, Class objectClass) throws SerializationException { return deserializeInternal(json, objectClass); } // We use this internally in situations where generic type checking isn't desirable static T deserializeInternal(String json, Class objectClass) throws SerializationException { try { return gson.fromJson(json, objectClass); } catch (Exception e) { throw new SerializationException(e); } } // Used internally to delegate to gson.toJson() in a way that will work correctly regardless of // whether we're shading the Gson types or not. // // The issue is this. In the Java SDK, all references to Gson types anywhere in the SDK *except* // in the LDGson class will have their packages rewritten from com.google.gson to // com.launchdarkly.shaded.com.google.gson. That's the whole reason GsonWriterAdapter exists. // However, the shading logic is not quite smart enough to adjust method signatures that have // already been copied into non-shaded classes, so if the LDGson code (which is immune from // shading) tries to call any methods on JsonSerialization.gson (which was originally an // instance of c.g.gson.Gson, but now is an instance of c.l.s.c.g.gson.Gson)-- or tries to call // any method that took a parameter of type c.g.gson.stream.JsonWriter, but has since been // rewritten to take a parameter of type c.l.s.c.g.gson.stream.JsonWriter-- the call will fail // because the actual method signature doesn't match what the caller expected. // // The solution is to add this delegating method whose external surface doesn't contain any // references to classes whose package names will be rewritten; while JsonSerialization and // GsonWriterAdapter will have code *inside* them modified by shading, their own signatures // won't change. static void serializeToGsonInternal(Object value, Class type, GsonWriterAdapter writer) { gson.toJson(value, type, writer); } // See comment on serializeToGsonInternal. static T deserializeFromGsonInternal(GsonReaderAdapter adapter, Type type) { return gson.fromJson(adapter, type); } /** * Internal method to return all of the classes that we should have a custom deserializer for. *

* The reason for this method is for some JSON frameworks, such as Jackson, it is not possible to * register a general deserializer for a base type like JsonSerializable and have it be called by * the framework when someone wants to deserialize some concrete type descended from that base type. * Instead, we must register a deserializer for each of the latter. *

* Since the SDKs may define their own JsonSerializable types that are not in this common library, * there is a reflection-based mechanism for discovering those: the SDK may define a class called * com.launchdarkly.sdk.json.SdkSerializationExtensions, with a static method whose signature is * the same as this method, and whatever it returns will be added to this return value. *

* In the case of a base class like LDValue where the deserializer is for the base class (because * application code does not know about the subclasses) and implements its own polymorphism, we * should only list the base class. * * @return classes we should have a custom deserializer for */ static Iterable> getDeserializableClasses() { // COVERAGE: This method should be excluded from code coverage analysis, because we can't test the // reflective SDK extension logic inside this repo. SdkSerializationExtensions is not defined in this // repo by necessity, and if we defined it in the test code then we would not be able to test the // default case where it *doesn't* exist. This functionality is tested in the Java SDK. synchronized (knownDeserializableClasses) { if (knownDeserializableClasses.isEmpty()) { knownDeserializableClasses.add(EvaluationReason.class); knownDeserializableClasses.add(EvaluationDetail.class); knownDeserializableClasses.add(LDUser.class); knownDeserializableClasses.add(LDValue.class); knownDeserializableClasses.add(UserAttribute.class); // Use reflection to find any additional classes provided by an SDK; if there are none or if // this fails for any reason, don't worry about it try { Class sdkExtensionsClass = Class.forName("com.launchdarkly.sdk.json.SdkSerializationExtensions"); Method method = sdkExtensionsClass.getMethod("getDeserializableClasses"); @SuppressWarnings("unchecked") Iterable> sdkClasses = (Iterable>) method.invoke(null); for (Class c: sdkClasses) { knownDeserializableClasses.add(c); } } catch (Exception e) {} } } return knownDeserializableClasses; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy