Stephan Schroeder
11/18/2019, 2:21 PMsealed class Data<T: Any> {
class Single<T: Any>@JsonCreator constructor(@JsonValue val single: T?): Data<T>()
class ListOf<T: Any>@JsonCreator constructor(@JsonValue val list: List<T>): Data<T>()
}
how do I created a correct typeRef for these to methods:
private fun <T: Any> readResponseWithSingleData(
jsonMapper: ObjectMapper,
json: String
): Response<T, Data.Single<T>> {
val typeRef = object : TypeReference<Response<T, Data.Single<T>>>() {}
return jsonMapper.readValue<Response<T, Data.Single<T>>>(json, typeRef)
}
private fun <T : Any> readResponseWithListOfData(
jsonMapper: ObjectMapper,
json: String
): Response<T, Data.ListOf<T>> {
val typeRef = object : TypeReference<Response<T, Data.ListOf<T>>>() {}
return jsonMapper.readValue<Response<T, Data.ListOf<T>>>(json, typeRef)
}
The current code will compile but because of type erasure the real type T will be discarded and a LinkedHashMap
is always used instead (which causes a ClassCast at runtime.)
I can’t make these methods inline and use reified types because I pass both methods as parameter.
I’m fairly certains that the answer is to use Jackson.TypeFactory.constructParametricType (https://fasterxml.github.io/jackson-databind/javadoc/2.9/com/fasterxml/jackson/databind/type/TypeFactory.html#constructParametricType-java.lang.Class-java.lang.Class...- )
It’s just that the generic parameters T is so deep enbedded (it’s a bit more complex that the example provided in the API with List<Set<T>>
). What would the answer be assuming that I’d pass a third argument clazz: Class<T>
to both methods?
Response
looks like this:
data class Response<T: Any, D: Data<T>> @JsonCreator constructor(
@JsonProperty("data") val data: Map<String, D>?,
@JsonProperty("errors") val errors: List<String>?
)
tseisel
11/18/2019, 2:33 PMTypeReference
which will specify the type of the generic parameter.
Have a look at this answer : https://stackoverflow.com/a/11681540/8691695Stephan Schroeder
11/18/2019, 2:42 PMreadResponseWithListOfData
)
val dataTypeRef = jsonMapper.getTypeFactory().constructParametricType(Data.ListOf::class.java, clazz)
but also type of Response which has two type parameters
val responseTypeRef = ...
return jsonMapper.readValue(json, responseTypeRef);
So the composition of the final type is my problem.Stephan Schroeder
11/18/2019, 2:50 PMprivate fun <T : Any> readResponseWithListOfData(
jsonMapper: ObjectMapper,
json: String,
clazz: Class<T>
): Response<T, Data.ListOf<T>> {
val typeRef = ...
return jsonMapper.readValue<Response<T, Data.ListOf<T>>>(json, typeRef)
}
Stephan Schroeder
11/18/2019, 3:31 PMval tType = jsonMapper.typeFactory.constructType(classOfT)
val tListType = jsonMapper.typeFactory.constructCollectionLikeType(List::class.java, classOfT)
val dataType = jsonMapper.typeFactory.constructParametricType(Data.ListOf::class.java, tListType)
val typeRef = jsonMapper.typeFactory.constructParametricType(Response::class.java, tType, dataType)
return jsonMapper.readValue<Response<T, Data.ListOf<T>>>(json, typeRef)
which leads to
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token
at [Source: (String)"{"data":{"offers":[{"offerId":"27","offerDetail":{"created":"2019-11-18T15:28:58.251","affiliateProgram":"PRICE_RUNNER"},"affiliateRetailer":{"affiliateProgram":"PRICE_RUNNER","name":"<http://Amazon.co.uk|Amazon.co.uk>","affiliateRetailerCode":"2137","created":"2019-11-18T10:27:00.593","lastModified":"2019-11-18T10:27:00.593"},"currencyCode":"GBP","stockStatus":"IN_STOCK","price":21.99,"commissionPercentage":0.0,"offerType":"AUTO","priceWithShippingMin":21.99,"priceWithShippingMax":21.99,"leadTime":"1.00 Day","affili"[truncated 861 chars]; line: 1, column: 20] (through reference chain: uk.co.digital.engine.Response["data"]->java.util.LinkedHashMap["offers"]->java.util.ArrayList[0])
jimn
11/18/2019, 5:20 PMcreate( Pair[] foo){
(ParameterizedType)pt=foo.getClass;
var magicTypeInfo=pt.getActualTypeArguments(){0]) //<---- do array factories here...
}
jimn
11/18/2019, 5:24 PMStephan Schroeder
11/19/2019, 10:31 AMjimn
11/19/2019, 10:39 AMStephan Schroeder
11/19/2019, 4:14 PMParameterizedType
is not in my classpath, I guess it’s a Gson-class? I don’t use Gson (but Jackson).Stephan Schroeder
11/19/2019, 4:27 PMval typeRef = ...
mapper.readValue<Data<T>>(json, typeRef)
I create a Wrapper for each actual type like this:
class Wrapper:Data<ActualType>()
mapper.readValue<Wrapper>(jsonString, Wrapper.class);
In endeffect I had to create 3 wrapper classes. It’s not ideal because those are basically boilerplate code, but at least they are straight forward to understand.jimn
11/19/2019, 6:01 PM