Unit testing static method- GsonSerializer - java

I need to junit test a piece of code, but the GsonConverter it calls from different class is in static method that i cannot change. I haven't a clue how to proceed as i cant mock it due to it being static.
public String fetchEntity(Object retValue, Object[] args) {
String refDet= null;
List<Details> updatedDetails = null;
if (retValue != null && retValue instanceof List && ((List) retValue).stream()
.noneMatch((o -> !(o instanceof Details)))) {
updatedDetails = (List<Details>) retValue;
} else {
logger.warn("Error");
return null;
}
try {
refDet= GsonConverter.serialize(updatedDetails );
} catch (Exception e) {
logger.warn("Error updatedDetails ");
}
return refDet;
}
Here is the class with static methods
class GsonConverter{
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(DateTime.class, (JsonDeserializer<DateTime>) (dateTime, type, context) -> ISODateTimeFormat.dateTime().parseDateTime(dateTime.getAsString()))
.create();
public static String serialize(Object o) {
return GSON.toJson(o);
}
}

The simplest thing would be to hide the direct use of GsonConverter behind an object instance. Perhaps something like:
interface JsonMapper {
String toJsonString(Object o);
}
class GsonJsonMapper implements JsonMapper {
String toJsonString(Object o) {
return GsonConverter.serialize(o);
}
}
Now in your original code, depend on the interface (JsonMapper) but instantiate it as an GsonJsonMapper (ideally using a dependency injection framework like Guice or Spring).
// declare an instance of type JsonMapper
private JsonMapper mapper;
public String fetchEntity(Object retValue, Object[] args) {
// skip the first part ...
try {
// use the mapper
refDet = mapper.serialize(updatedDetails );
} catch (Exception e) {
logger.warn("Error updatedDetails ");
}
return refDet;
}
Now you have the ability to mock out the JsonMapper interface.
You will often encounter this type of situation -- code that was not written to be testable often must change in order to add tests. Which is why many developers practice TDD, or at least write unit tests immediately after writing the new code.

Related

How do i get an object of a response which is an empty string instead of an object when it doesnt exist? [duplicate]

Here is the json schema:
As you can see, rated can be both boolean and object.
I am using Retrofit 2 and Gson converter. How should I create my model for this schema?
Here's how I solved this issue:
Create a custom type adapter in your model and parse rated manually;
public class AccountState {
//#SerializedName("rated") //NOPE, parse it manually
private Integer mRated; //also don't name it rated
public Integer getRated() {
return mRated;
}
public void setRated(Integer rated) {
this.mRated = rated;
}
public static class AccountStateDeserializer implements JsonDeserializer<AccountState> {
#Override
public AccountState deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
AccountState accountState = new Gson().fromJson(json, AccountState.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("rated")) {
JsonElement elem = jsonObject.get("rated");
if (elem != null && !elem.isJsonNull()) {
if(elem.isJsonPrimitive()){
accountState.setRated(null);
}else{
accountState.setRated(elem.getAsJsonObject().get("value").getAsInt());
}
}
}
return accountState ;
}
}
}
Here you create your gson with this custom adapter:
final static Gson gson = new GsonBuilder()
.registerTypeAdapter(AccountState.class, new AccountState.AccountStateDeserializer())
.create();
Add it to retrofit like that:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BuildConfig.ENDPOINT)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build();
TADADADADADADADDAD!
You can make it work without having to implement a custom converter.
All you have to do is put a general "Object" type for the variable and then you just check which data type it is by doing this:
if(object.getClass == YourClass.class){
Whatever we = ((YourClass) object).getWhatever();
} else if(object.getClass == YourOtherClass.class){
String name = ((YourOtherClass) object).getName();
}
You can add as many data types to this variable as you like.
You can also use the java types "String.class", "Boolean.class" or whatever you like.
Gson has a nice feature allowing to inject a custom type adapter or a type adapter factory to a certain field therefore letting Gson to manage the host object and the latter's fields (de)serialization. So, you can be sure that AccountState could be still deserialized with ReflectiveTypeAdapterFactory and ReflectiveTypeAdapterFactory.Adapter so all deserialization strategies defined in GsonBuilder could be applied.
final class AccountState {
// This is what can make life easier. Note its advantages:
// * PackedBooleanTypeAdapterFactory can be reused multiple times
// * AccountState life-cycle can be managed by Gson itself,
// so it can manage *very* complex deserialization automatically.
#JsonAdapter(PackedBooleanTypeAdapterFactory.class)
final Boolean rated = null;
}
Next, how PackageBooleanTypeAdapterFactory is implemented:
final class PackedBooleanTypeAdapterFactory
implements TypeAdapterFactory {
// Gson can instantiate this itself, no need to expose
private PackedBooleanTypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// Check if it's the type we can handle ourself
if ( typeToken.getRawType() == Boolean.class ) {
final TypeAdapter<Boolean> typeAdapter = new PackedIntegerTypeAdapter(gson);
// Some Java "unchecked" boilerplate here...
#SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
return castTypeAdapter;
}
// If it's something else, let Gson pick a downstream type adapter on its own
return null;
}
private static final class PackedIntegerTypeAdapter
extends TypeAdapter<Boolean> {
private final Gson gson;
private PackedIntegerTypeAdapter(final Gson gson) {
this.gson = gson;
}
#Override
public void write(final JsonWriter out, final Boolean value) {
throw new UnsupportedOperationException();
}
#Override
public Boolean read(final JsonReader in)
throws MalformedJsonException {
// Pick next token as a JsonElement
final JsonElement jsonElement = gson.fromJson(in, JsonElement.class);
// Note that Gson uses JsonNull singleton to denote a null
if ( jsonElement.isJsonNull() ) {
return null;
}
if ( jsonElement.isJsonPrimitive() ) {
return jsonElement
.getAsJsonPrimitive()
.getAsBoolean();
}
if ( jsonElement.isJsonObject() ) {
return jsonElement
.getAsJsonObject()
.getAsJsonPrimitive("value")
.getAsBoolean();
}
// Not something we can handle
throw new MalformedJsonException("Cannot parse: " + jsonElement);
}
}
}
Demo:
public static void main(final String... args) {
parseAndDump("{\"rated\":null}");
parseAndDump("{\"rated\":true}");
parseAndDump("{\"rated\":{\"value\":true}}");
}
private static void parseAndDump(final String json) {
final AccountState accountState = gson.fromJson(json, AccountState.class);
System.out.println(accountState.rated);
}
Output:
null
true
true
Note that JsonSerializer and JsonDeserializer both have some performance and memory cost due to its tree model design (you can traverse JSON trees easily as long as they are in memory). Sometimes, for simple cases, a streaming type adapter may be preferable. Pros: consumes less memory and works faster. Cons: hard to implement.
final class AccountState {
#JsonAdapter(PackedBooleanTypeAdapter.class)
final Boolean rated = null;
}
Note that the rated field accepts a type adapter directly because it does not need Gson instances to build JSON trees (JsonElements).
final class PackedBooleanTypeAdapter
extends TypeAdapter<Boolean> {
// Gson still can instantiate this type adapter itself
private PackedBooleanTypeAdapter() {
}
#Override
public void write(final JsonWriter out, final Boolean value) {
throw new UnsupportedOperationException();
}
#Override
public Boolean read(final JsonReader in)
throws IOException {
// Peeking the next JSON token and dispatching parsing according to the given token
final JsonToken token = in.peek();
switch ( token ) {
case NULL:
return parseAsNull(in);
case BOOLEAN:
return parseAsBoolean(in);
case BEGIN_OBJECT:
return parseAsObject(in);
// The below might be omitted, since some code styles prefer all switch/enum constants explicitly
case BEGIN_ARRAY:
case END_ARRAY:
case END_OBJECT:
case NAME:
case STRING:
case NUMBER:
case END_DOCUMENT:
throw new MalformedJsonException("Cannot parse: " + token);
// Not a known token, and must never happen -- something new in a newer Gson version?
default:
throw new AssertionError(token);
}
}
private Boolean parseAsNull(final JsonReader in)
throws IOException {
// null token still has to be consumed from the reader
in.nextNull();
return null;
}
private Boolean parseAsBoolean(final JsonReader in)
throws IOException {
// Consume a boolean value from the reader
return in.nextBoolean();
}
private Boolean parseAsObject(final JsonReader in)
throws IOException {
// Consume the begin object token `{`
in.beginObject();
// Get the next property name
final String property = in.nextName();
// Not a value? Then probably it's not what we're expecting for
if ( !property.equals("value") ) {
throw new MalformedJsonException("Unexpected property: " + property);
}
// Assuming the property "value" value must be a boolean
final boolean value = in.nextBoolean();
// Consume the object end token `}`
in.endObject();
return value;
}
}
This one should work faster. The output remains the same. Note that Gson does not require a GsonBuilder for both cases. As far as I remember how Retrofit 2 works, GsonConverterFactory is still required (not sure, Gson is not the default serializer in Retrofit 2?).

How do I parametrize response parsing in Java?

I'm writing a network class and want to be able to parse different responses to different classes (there's still one-to-one relationship but I want to have a single parseResponse() that will deal with all responses from different endpoints, and endpoint.className has the expected classType that I should map to):
private Class<?> parseResponse(StringBuilder responseContent, Endpoint endpoint) {
ObjectMapper mapper = new ObjectMapper();
try {
Class<?> object = mapper.readValue(responseContent.toString(), endpoint.className);
// endpoint.className has Class<?> type
if (object instanceof endpoint.className) {
}
} catch (IOException e) {
// handle errors
}
}
But there's an error if I write if (object instanceof endpoint.className)
Update: probably the better option is to add parse() method to Endpoint class:
public Class<?> parseResponse(String responseContent) {
// this.className has Class<?> type (e.g., Foo.class).
}
public enum Endpoint {
FOO (Foo.class),
BAR (Bar.class);
private Class<?> classType;
}
But there're still the same type errors.
You should separate JSON deserialisation from other parts of your app. You can not implement one method for all responses but you probably have a limited number of responses and you can declare some simple methods for each class. Generally, you could have only one method with declaration like below:
public <T> T deserialise(String payload, Class<T> expectedClass) {
Objects.requireNonNull(payload);
Objects.requireNonNull(expectedClass);
try {
return mapper.readValue(payload, expectedClass);
} catch (IOException e) {
throw new IllegalStateException("JSON is not valid!", e);
}
}
And now, you can deserialise all payloads you want. You need to provide JSON payload and POJO class you want to receive back.
Simple working solution which shows that concept:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.IOException;
import java.util.Objects;
public class JsonMapper {
private final ObjectMapper mapper = new ObjectMapper();
public JsonMapper() {
// configure mapper instance if required
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// etc...
}
public String serialise(Object value) {
try {
return mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Could not generate JSON!", e);
}
}
public <T> T deserialise(String payload, Class<T> expectedClass) {
Objects.requireNonNull(payload);
Objects.requireNonNull(expectedClass);
try {
return mapper.readValue(payload, expectedClass);
} catch (IOException e) {
throw new IllegalStateException("JSON is not valid!", e);
}
}
public Foo parseResponseFoo(String payload) {
return deserialise(payload, Foo.class);
}
public Bar parseResponseBar(String payload) {
return deserialise(payload, Bar.class);
}
public static void main(String[] args) {
JsonMapper jsonMapper = new JsonMapper();
String bar = "{\"bar\" : 2}";
System.out.println(jsonMapper.parseResponseBar(bar));
String foo = "{\"foo\" : 1}";
System.out.println(jsonMapper.parseResponseFoo(foo));
System.out.println("General method:");
System.out.println(jsonMapper.deserialise(foo, Foo.class));
System.out.println(jsonMapper.deserialise(bar, Bar.class));
}
}
class Foo {
public int foo;
#Override
public String toString() {
return "Foo{" +
"foo=" + foo +
'}';
}
}
class Bar {
public int bar;
#Override
public String toString() {
return "Bar{" +
"bar=" + bar +
'}';
}
}
See also:
Deserializing or serializing any type of object using Jackson ObjectMapper and handling exceptions
What are Reified Generics? How do they solve Type Erasure problems and why can't they be added without major changes?
How to use jackson to deserialize to Kotlin collections

Gson, can't serialize/deserialize class type

I've got a class like this:
public class A {
#serializedName("type")
Class<?> type;
...
}
But when I tried to serialize it I get an error saying "Attempt to serialize java.lang.class: java.lang.String. Forgot to register a TypeAdapter?". So I created this adapter:
public class MyTypeAdapter extends TypeAdapter<Class> {
public Class read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
} else {
String className = in.nextString();
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new JsonParseException("class " + className + " not found");
}
}
}
public void write(JsonWriter out, Class value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(value.getName());
}
}
}
And registered it like this:
new GsonBuilder().registerTypeAdapter(Class.class, new MyTypeAdapter ()).create().
fromJson(value, listType);
But I'm still getting the same error.
What am I doing wrong?
Does the implementation of the adapter look ok?
What am I doing wrong?
Gson takes type information into account: you're trying to mix Class and Class<?> that are different types representing a raw type and a wildcard-parameterized type respectively.
From this perspective, Gson does not consider Class (found in your registerTypeAdapter) and Class<?> (found in your DTOs) equivalent.
For this case you have to register the type hierarchy adapter with registerTypeHierarchyAdapter.
Does the implementation of the adapter look ok?
Yes, but it can be improved slightly:
final class ClassTypeAdapter
extends TypeAdapter<Class<?>> {
// The type adapter does not hold state, so it can be easily made singleton (+ making the constructor private)
private static final TypeAdapter<Class<?>> instance = new ClassTypeAdapter()
// This is a convenient method that can do trivial null-checks in write(...)/read(...) itself
.nullSafe();
private ClassTypeAdapter() {
}
static TypeAdapter<Class<?>> get() {
return instance;
}
#Override
public void write(final JsonWriter out, final Class<?> value)
throws IOException {
// value is never a null here
out.value(value.getName());
}
#Override
public Class<?> read(final JsonReader in)
throws IOException {
try {
// This will never be a null since nullSafe() is used above
final String className = in.nextString();
return Class.forName(className);
} catch ( final ClassNotFoundException ex ) {
// No need to duplicate the message generated in ClassNotFoundException
throw new JsonParseException(ex);
}
}
}
As it's said by #Daniel Pryden, this is potentially a huge security issue, because Class.forName may execute code (static initializers).
You should check the className against the classes whitelist before Class.forName(...) is executed.
Also note that Class instances do not hold type parameterization (please see what TypeTokens and ParameterizedTypes are for) and you might want to encode the type with all of its type parameterization (easy to toString(...) but not that easy to parse though - I faced such an issue once and resolved it by implementing a parser in JParsec).

Design to support request processing as per object types

As per an use-case, this is what we do:
Expose multiple Async service APIs with different arguments (ex. InputObject1, InputObject2 etc.).
Client calls these APIs with proper input type and we send response back to client and push input object in a queue (ex. SQS) in JSON form (using Gson).
Another poller, keeps on polling the queue and receives messages from the queue. On receiving the message, poller has to do the task as per inputObject type.
There are two places my code could look dirty:
a. How to check the type of object on receiving from the queue? This would be in JSON format and I will have to convert JSON back to object. It will belong to one of the multiple potential objects.
b. Once type is known, how to call class responsible to handle that object?
What could be the optimal design for this use-case?
For a), one option is to create a `RequestWrapper` containing all the objects and populating the one this message belongs to.
Couple of ways I can think for b) are as following:
1. Add another parameter to the object and pass it to queue. Use this parameter to identify API called.
2. Use `instanceof` to get exact requestObject using multiple if-else and do the needful.
Though, these don't seem very neat to me. Any better suggestions?
==Edit==
#A4L
No, they don't share any common interface (at least, as of now).
Yes, we can modify these objects in the start (if that's what you mean by "implementation"). Since, we can change this, I can make them share a common interface, if required.
Thanks,
I would suggest to introduce at least one new interfaces QueueTaskAble which will be implement by your input objects and a second - optional, could simply be java.lang.Runnable - which then executes the Task with some sort of run or execute method.
Here is how it could look like:
interface QueueTaskAble {
Runnable getQueueTask();
}
class InputObjectFooQueueTask implements Runnable {
#Override
public void run() {
// TODO Auto-generated method stub
}
}
class InputObjectFooImpl implements QueueTaskAble {
#Override
public Runnable getQueueTask() {
return new InputObjectFooQueueTask();
}
}
void processQueueInputObject(QueueTaskAble queueObject) {
queueObject.getQueueTask().run();
}
EDIT
Unfortunately it is not possible to natively deserialize as interface using Gson. To be able to do so you need to implement a type adapter which you can pass to GsonBuilder#registerTypeAdapter so that your object are properly serialized and deserialized.
Here is how you could go about it:
The type adapter
public class GenericGsonTypeAdapter<T> implements JsonSerializer<T>,
JsonDeserializer<T> {
#Override
public JsonElement serialize(T src, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject jo = new JsonObject();
jo.addProperty("className", src.getClass().getName());
jo.add("jsonData", context.serialize(src));
return jo;
}
#Override
public T deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
T obj = null;
if(json instanceof JsonObject) {
JsonObject jo = (JsonObject) json;
JsonElement jeJson = jo.get("jsonData");
if(jeJson != null) {
JsonElement jeClassName = jo.get("className");
try {
obj = context.deserialize(json,
Class.forName(jeClassName.getAsString()));
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
}
return obj;
}
}
A custom Gson builder (unfortunately GsonBuilder is final and thus cannot be extended, hence as static final member)
public class InputObjectGsonBuilder {
private final static GsonBuilder gb;
static {
gb = new GsonBuilder();
gb.registerTypeAdapter(QueueTaskAble.class,
new GenericGsonTypeAdapter<QueueTaskAble>());
}
public Gson create() {
return gb.create();
}
}
A sample queue
public class InputObjectGsonQueue {
private Queue<String> queue = new ArrayDeque<>();
public boolean pushInputObject(String json) {
return queue.offer(json);
}
public void processQueue() {
InputObjectGsonBuilder gb = new InputObjectGsonBuilder();
String json;
while(null != (json = queue.poll())) {
QueueTaskAble queueTaskAble = gb.create().fromJson(json,
QueueTaskAble.class);
processQueueInputObject(queueTaskAble);
}
}
private void processQueueInputObject(QueueTaskAble queueObject) {
queueObject.getQueueTask().run();
// or for asynchronous processing
// new Thread(queueObject.getQueueTask()).start();
}
}
Some input objects and tasks implementations
public class InputObjectFooImpl implements QueueTaskAble {
#Override
public Runnable getQueueTask() {
return new InputObjectFooTaksImpl();
}
}
public class InputObjectBarImpl implements QueueTaskAble {
#Override
public Runnable getQueueTask() {
return new InputObjectBarTaksImpl();
}
}
public class InputObjectFooTaksImpl implements Runnable {
#Override
public void run() {
System.out.println("Foo!");
}
}
public class InputObjectBarTaksImpl implements Runnable {
#Override
public void run() {
System.out.println("Bar!");
}
}
And finally a sample application
public class App {
public static void main(String... args) {
InputObjectGsonBuilder gb = new InputObjectGsonBuilder();
InputObjectGsonQueue gq = new InputObjectGsonQueue();
gq.pushInputObject(gb.create().toJson(new InputObjectFooImpl(),
QueueTaskAble.class));
gq.pushInputObject(gb.create().toJson(new InputObjectBarImpl(),
QueueTaskAble.class));
gq.processQueue();
}
}
Output
Foo!
Bar!

How to return the proper object type after deserialization?

I'm currently working on a serialization class for my code library.
This is to avoid having to write the same code over and over again.
Because this is part of my code library i never know what kind of object class i'm deserializing.
I was wondering of anyone could update my code to have the return value be the kind of class i provide in the method variable.
Hope it makes sense :)
public static Object deserializeObject(File serializedFile) {
Object returnObject;
if (!serializedFile.exists() || !serializedFile.canRead()) {
return null;
}
try {
//use buffering
InputStream file = new FileInputStream(serializedFile);
InputStream buffer = new BufferedInputStream(file);
ObjectInput input = new ObjectInputStream(buffer);
try {
//deserialize the List
returnObject = input.readObject();
} finally {
input.close();
}
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
return null;
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
return returnObject;
}
You could declare the method like this:
public static <T> T deserializeObject(File serializedFile, Class<T> type) {
...
return (T) returnObject; // Or return type.cast(returnObject);
}
When you are calling the method, you could then use
MyObj myObj = deserializeObject(file, MyObj.class);
Do you want to return the class of the deserialized object?
if so you could just do
return returnObject.getClass().getName();
You can use the instanceof check to see what type of object you are passing in, then based on that case it to the appropriate type.
public static void doSomething(Animal aAnimal){
if (aAnimal instanceof Fish){
Fish fish = (Fish)aAnimal;
fish.swim();
}
else if (aAnimal instanceof Spider){
Spider spider = (Spider)aAnimal;
spider.crawl();
}
}
You could also add another parameter as input to your method and pass in the class of object you want to deserialize. Normally I would name it something like Class clazz.
public static Object deserializeObject(Class clazz, File serializedFile)
If you pass the type of class in, you could cast your object using that value.

Categories

Resources