Gson Unsafe Problem

The Problem

Example

public class Parameter {
private final int id;
private final String name;
private boolean isRequired = true;

public Parameter(int id, String name) {
this.id = id;
this.name = name;
}

public int getId() {
return id;
}

public String getName() {
return name;
}

public boolean isRequired() {
return isRequired;
}
}

Description

data class Car(
val id: Int,
val name: String,
val isPopular: Boolean = true,
val brand: Brand = UnknownBrand()
)
data class Cars(
private val data: List<Car>
) {
val sorted: List<Car> by lazy { data.sortedBy { it.name } }
}

Popular Possible Way

data class Car(
val id: Int,
val name: String,
val isPopular: Boolean = true,
val brand: Brand = UnknownBrand()
) {
private constructor() : this(0, "") //or other default values
}

Solution

What’s happening in Jackson

public class NonDefaultBean {
private final String name;
private final int age;

private String type;

@JsonCreator
public NonDefaultBean(@JsonProperty("name") String name, @JsonProperty("age") int age)
{
this.name = name;
this.age = age;
}

public void setType(String type) {
this.type = type;
}
}

Main Solution Frame

class ConstructorInvocationDeserializer<T>(
private val fields: Array<String>
) : JsonDeserializer<T> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): T {
val clazz = TypeToken.get(typeOfT).rawType
var args: Array<Any?> = arrayOf()
val appropriateConstructor = clazz.constructors
.filter { it.parameterTypes.size == fields.size }
.find {
try
{
args = deserializeEach(json!!.asJsonObject, it.parameterTypes, context)
true
} catch (e: JsonSyntaxException) {
false
}
}
return
appropriateConstructor!!.newInstance(*args) as T
}

@Throws(JsonSyntaxException::class)
private fun deserializeEach(json: JsonObject, fieldTypes: Array<Class<*>>, context: JsonDeserializationContext): Array<Any?> {
val result = Array<Any?>(fields.size, {null})
fields.forEachIndexed { index, name ->
result[index] = context.deserialize(json.get(name), fieldTypes[index])
}
return
result
}
}
val gson = GsonBuilder()
.registerTypeAdapter(Car::class.java, ConstructorInvocationDeserializer<Car>(arrayOf("id", "name")))
.create()

Problems with that solution

  1. Necessity to register Adapter for each new type (you may forget to do that and thus the bug will be produced)
  2. Lack of constructor variations. If we have a model with 2 or 3 constructors. How we will tell to our adapter to find appropriate constructor according to received Json
  3. Fields injected only through constructor. So if Json has any additional fields they will be not applicable for created object, that has appropriate fields but not in the constructor.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Michael Spitsin

Michael Spitsin

Love being creative to solve some problems with an simple and elegant ways