I'm writing a storage manager. I want to read from file to object. Example:
protected Object read(String key) throws Exception{
Path pathReadFrom = Paths.get(getPath(key));
if (!Files.isReadable(pathReadFrom)){
throw new FileNotFoundException();
}
Object object = JSON_MAPPER.readValue(Files.readAllBytes(pathReadFrom), Object.class);
return object;
}
JSON_MAPPER is Jackson's ObjectMapper.
public MyClass get(String id) throws Exception {
MyClass myClassObject = (MyClass) storeManager.read(id);
return myClassObject;
}
I get the next exception:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.pckg.project.dto.MyClass
How can I create universal read() method? Maybe should I set a type like second argument of read() method?
I've thought up some solution:
protected <T> T read(String key, Class<T> valueType) throws Exception{
Path pathReadFrom = Paths.get(getPath(key));
T object = JSON_MAPPER.readValue(Files.readAllBytes(pathReadFrom), valueType);
return object;
}
public MyClass get(String id) throws Exception {
MyClass object = storeManager.read(id, MyClass.class);
return object;
}
It works fine.
Yes, you do need to specify proper target type to use: otherwise Jackson has no idea of what kind of class you might want to get -- you are just telling it to "create a java.lang.Object out of JSON", which it happily does by using so-called "untyped" mode: JSON Objects become java.util.Maps, JSON Arrays java.util.Lists, JSON Strings regular Java Strings and so on.
So if you want to get JSON bound to Class MyClass, you will pass MyClass.class instead of Object.class.
How can you cast unrelated objects? In what way are LinkedHashMap and MyClass are related? You need to map them manually (or may be with help of something equivalent of AutoMapper for .NET)
Related
I have data structure that looks more or less like this
class ResponseWrapper<T> {
T response;
public ResponseWrapper(T response) {
this.response = response;
}
}
And service that handles reading that response from JSON to actual DTO.
public class GenericService<T> {
public ResponseWrapper<T> read(String json, Class<T> clazz) throws Exception {
T response = new ObjectMapper().readValue(json, clazz);
return new ResponseWrapper<>(response);
}
}
And I can call it like this:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value", SomeData.class)
And what I'm trying to achieve is:
GenericResponse<SomeData> response = new GenericService<SomeData>().read("json value")
And I'm wondering, is it actually possible? This is obviously not working
public ResponseWrapper<T> read(String json) throws Exception {
T response = new ObjectMapper().readValue(json, T.class);
return new ResponseWrapper<>(response);
}
No. It is not possible.
Java generics work by type erasure ... and that means that the actual class associated with generic type parameter is not available at runtime. If your code needs to know that class, you need to pass a Class object explicitly.
And, yes, T.class is a compilation error.
And, yes, there is no way to get the class of T.
Whats the difference between using Class.cast to convert an object to a certain type v/s doing the same using ObjectMapper.convertValue.
I am assuming cast also internally uses jackson but I think that's not the case here.
My RedisTemplateConfig:
#Bean
public ReactiveRedisTemplate<String, Object> reactiveRedisTemplate(
ReactiveRedisConnectionFactory factory) {
StringRedisSerializer keySerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object> valueSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
RedisSerializationContext.RedisSerializationContextBuilder<String, Object> builder =
RedisSerializationContext.newSerializationContext(keySerializer);
RedisSerializationContext<String, Object> context =
builder.value(valueSerializer).build();
return new ReactiveRedisTemplate<>(factory, context);
}
SetValueInRedis:
#Override
public <T> Mono<T> setValue(String key, Object value, Class<T> clazz) {
return reactiveValueOps.set(key, value,
Duration.ofDays(SESSION_PERSISTENCE_DURATION))
.map(o -> clazz.cast(value));
}
Working GetValueInRedis:
#Override
public <T> Mono<T> getValue(String key, Class<T> clazz) {
return reactiveValueOps.get(key)
.flatMap(val -> Mono.justOrEmpty(objectMapper.convertValue(val, clazz)));
}
Error version of Get Value:
#Override
public <T> Mono<T> getValue(String key, Class<T> clazz) {
return reactiveValueOps.get(key)
.flatMap(o -> Mono.justOrEmpty(clazz.cast(o)));
}
----EDIT----
If we notice the serializer used for Redis(Jackson2JsonRedisSerializer): while saving the object to redis it works fine. But while reading(get) cast fails and objectMappper works. When I am using Jackson2JsonRedis serializer, shouldn't get command return an object which should be castable using Class.cast command itself?
Class.cast and ObjectMapper.convertValue are two totally different mechanisms. Class.cast does not create new object, only returns old reference with new type. Below you can see how Class.cast is implemented:
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException(cannotCastMsg(obj));
return (T) obj;
}
Jackson from other side creates new object from different hierarchy and copy internal structure. For example, can convert Map to POJO and POJO to Map using reflection. But you can not cast reference to Map on reference to POJO.
Summary: only convertValue method from these two really does conversion from one instance to new one and from one type to new one. cast only tries to change reference type to the same object.
See also:
Java Class.cast() vs. cast operator
Convert a Map to a POJO
How does Java Object casting work behind the scene?
Java casting implementation
How does the Java cast operator work?
How i can deserialize my object from json string, if i have only string name of my class, by canonical name invoke, and json string representation of object?
In other words, i need method like this:
public <T> T deserialize(String json, String className) {
// some code
}
We have class name and json string - and we need to deserialize json to java object.
I know how to deserialize json to object if i know the class of object, for example, we can use Jackson:
String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
Car car = objectMapper.readValue(json, Car.class);
But in my task there is a different classes - it may be Cat.class, Dog.class, Car.class and etc. And every class with different state. And i need universal method for deserializing.
I write something like:
public final class Utils implements Serializable {
private Utils () {
throw new AssertionError();
}
private static ObjectMapper objectMapper = new ObjectMapper();
public static String toJson(Object obj) {
String objectToJson;
try {
objectToJson = objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new UncheckedIOException(String.format("Can't serialize object %s to JSON string", obj), e);
}
return objectToJson;
}
public static <T> T fromJson(String json, String className) throws ClassNotFoundException, IOException {
Class<?> clz = Class.forName(className);
return (T) objectMapper.readValue(json, clz);
}
}
And it works with example of car above, but with a warning cast exception.
But maybe there is a better way?
Oh, and not all objects will be so simple. Some of them would encapsulate collections and other objects.
The problem stems from the compiler wanting to (virtually) make a different instance of this method for every type T that it is used on. However, there is nothing in the arguments to hang its hat on.
If the caller doesn't know what class to expect, other than a String representation of that class (which I'm assuming has been saved in the JSON as well?), what is he going to do with the result of this call? Since he doesn't know, he must be using it as simply Object, in which case the method might as well simply return Object.
If the caller does know the class, then he can pass in a Class as the argument, and you won't have any issues.
It occurred to me as writing this that ObectMapper must have a similar problem for some of its readValue methods, such as the one that accepts ResolvedType instead of Class. I notice that, in that class' source code, it includes /unchecked/ before the class. I"m not sure if that is to get around this issue or not, but it makes sense.
I am testing a private method using JUnit and I am invoking it using Reflection. The error I am getting is java.lang.InstantiationException. I know it is not creating an instance of Class but I am not sure what I am doing wrong. Object object = clazz.newInstance(); is the line that throws Exception.
Method under test
private int _getType(String type) {
if ("DATE".equalsIgnoreCase(type)) return Types.DATE;
if ("STRING".equalsIgnoreCase(type)) return Types.VARCHAR;
if ("INT".equalsIgnoreCase(type)) return Types.INTEGER;
if ("TIMESTAMP".equalsIgnoreCase(type)) return Types.TIMESTAMP;
return Types.NULL;
}
JUnit test
#Test
public void testGetType() throws Exception {
String type1 = "DATE";
String type2 = "STRING";
String type3 = "INT";
String type4 = "TIMESTAMP";
Class clazz = SpringStoredProcImpl.class;
Object object = clazz.newInstance();
Method method = object.getClass().getDeclaredMethod("getType", String.class);
method.setAccessible(true);
method.invoke(object, type1);
I don't have my asserts yet so please ignore it.
Thanks in advance.
You try to create an instance with a no argument constructor which does exist in your case.
As the constructors are public you should first create your object normally using the new keyword, then execute the rest of your code starting from Method method...
FYI, if you wanted to create your object by reflection it would be something like clazz.getConstructor(DataSource.class, String.class, ArrayList.class).newInstance(dataSource, sprocName, params) instead of simply clazz.newInstance()
Different answer: don't do that.
Why do you think you need to make this method private? It looks like the responsibility of this method is to "lookup" an enum type, based on string input. I think it would make much more sense to simply make this method package protected and avoid the reflection overhead/hassle.
I am using Jackson to parse object. sometime I need list of objects.
when I am using like this its working
List<MyObject> mapper.readValue(file , new TypeReference<MyObject>() {})
but when I am using it like this its not working
public class JsonMocksRepository<T>{
public T getObject() throws Exception{
return mapper.readValue(file ,new TypeReference<T>());
}
}
What I need to do ?
Basically I want to use generics to get the right class
This is because of type erasure. There is no information about the actual type represented by T available at runtime, so your TypeReference will be effectively be simply TypeReference<Object>.
If you want a generic instance of JsonMocksRepository, you will need to inject the TypeReference at construction time:
public class JsonMocksRepository<T>{
private final TypeReference<T> typeRef;
public JsonMocksRepository(TypeReference<T> typeRef) {
this.typeRef = typeRef;
}
public T getObject() throws Exception{
return mapper.readValue(file, typeRef);
}
}