Can not cast deserialized json array with jackson - java

I was working with jackson lib in java to deserialize an json file to an array list.
First I use this method and everything worked fine.
ObjectMapper objectMapper = new ObjectMapper();
ArrayList<User> users = (ArrayList<User>) objectMapper.readValue(new File("data.json"), new TypeReference<List<User>>() {});
Then I decided to refactor the code and write a generic method to use for any type of data.
public static <T> ArrayList<T> listFromJson(String filename) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
return (ArrayList<T>) objectMapper.readValue(new File(filename), new TypeReference<List<T>>() {});
}
This method returns the array list without any exceptions. But when I want to use an element of arraylist and store it in a variable the program throws exception like this.
User user = users.get(0);
Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class org.example.User ...
....
I also tried to print out the element without casting and it wasn't an object reference. It was something like a hashmap.
I think it is related to generics but I don't know the cause.
Thanks for your help.

Your object User is a JsonNode, something like this:
{
"userName": "...",
"email": "...",
etc.
}
While this object can be mapped against a User.class if you specify so, when you don't say anything, the only guess that Jackson can take is that this is a Map<String, Object> (and the implementation a LinkedHashMap, meaning a map respecting its insertion order).
So when you refactored your method:
public static <T> ArrayList<T> listFromJson(String filename) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
return (ArrayList<T>) objectMapper.readValue(new File(filename), new TypeReference<List<T>>() {});
}
... you said to Jackson that it will find a List<T> where T can be any object. The only guess it can take is it will be a JsonNode (hence a Map.class).
To solve this issue, you should pass the concrete TypeReference to the method as well:
public static <T> ArrayList<T> listFromJson(String filename, TypeReference<?> expectedTypeReference) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
return (List<T>) objectMapper.readValue(new File(filename), expectedTypeReference);
}
... and use it like this:
TypeReference<List<User>> expectedTypeRef = new TypeReference<>() {};
List<User> users = listFromJson(fileName, expectedTypeRef);
User user = users.get(0); // <-- this should work

Related

how to convert list fields to map in java

I'm getting an error trying to change a Java object to a map. It is assumed that there is a list among the fields. Is there any solution???
static class User {
private String name;
private List<Integer> ages;
}
#Test
void t3() {
final ObjectMapper objectMapper = new ObjectMapper();
final User kim = new User("kim", Arrays.asList(1, 2));
objectMapper.convertValue(kim, new TypeReference<Map<String, List<Object>>>() {}); // error
// objectMapper.convertValue(kim, new TypeReference<Map<String, Object>>() {}); // not working too
}
error message:
Cannot deserialize value of type java.lang.String from Array value (token JsonToken.START_ARRAY)
Your fields have visibility private, by default the object mapper will only access properties that are public or have public getters/setters. You can either create getter and setters if it fits your use-case or change the objectMapper config
// Before Jackson 2.0
objectMapper.setVisibility(JsonMethod.FIELD, Visibility.ANY);
// After Jackson 2.0
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
More about it - http://fasterxml.github.io/jackson-databind/javadoc/2.5/com/fasterxml/jackson/databind/ObjectMapper.html#setVisibility(com.fasterxml.jackson.annotation.PropertyAccessor,%20com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility) and https://www.baeldung.com/jackson-jsonmappingexception
and then use Use Map<String, Object> as the TypeReference
// Create ObjectMapper instance
ObjectMapper objectMapper = new ObjectMapper();
// Converting POJO to Map Map<String, Object>
map = objectMapper.convertValue(kim, new TypeReference<Map<String, Object>>() {});
// Convert Map to POJO
User kim2 = objectMapper.convertValue(map, User.class);
Let me know if you face any other issue.

How to convert to json to object by passing the object type

public static Object convertToObject(String json) {
validate("json", json);
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(json, new TypeReference<Object>(){});
} catch (JsonProcessingException ex) {
throw new RuntimeException("Failed to convert json to object: "+json, ex);
}
}
//Here I'm doing type cast for the returned object
Map<String, ArrayList<Integer>> convertedMap = (Map<String, ArrayList<Integer>>) RestClient.convertToObject(json);
I tried above code
I'm using jackson and I need to pass the json with type so that I can avoid the type cast in the caller place.
Can anyone help me in this?
Thanks in advance
Object mapper requires 2nd Parameter as a Class type, so try passing:
Object.class
as below:
return mapper.readValue(json, Object.class);
First, you should use this method in common class for Mapper initialization
public static ObjectMapper getJacksonMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT);
mapper.setVisibility(VisibilityChecker.Std.defaultInstance().withFieldVisibility(JsonAutoDetect.Visibility.ANY));
return mapper;
}
Second thing, you should create POJO class for the corresponding json. Finally you can get the json string has to be converted as POJO class and you can use it very easily.
SamplePOJO poListResponse = AppUtils.getJacksonMapper().readValue(jsonString, SamplePOJO.class);

Java Generics : Object Mapper Converting JSON to Java Object

I would like to deserialize a json string to a java object. I wanted to write a generic method.
public class ObjectSerializer<T> {
T t;
private ObjectMapper mapper = new ObjectMapper();
/* public Person deSerial(String json) throws Exception {
TypeReference<Person> typeRef = new TypeReference<Person>() {};
return mapper.readValue(json, typeRef);
} */
public T deSerialize(String jsonInput) throws Exception {
TypeReference<T> typeRef
= new TypeReference<T>() {};
return mapper.readValue(jsonInput, typeRef);
}
}
When I call deSerialize(validPersonJsonString) [validPersonJsonString : valid person JSON String], it is not working, it it throwing me the error:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.Person.
Whereas, when in call the commented deSerial method, it works fine. Please explain the issue.
Thanks.
Jackson doesn't support TypeReference with generic type parameters because of type erasure. Here's the bug about it.
As far as your use case is concerned, you don't need to use ObjectSerializer class at all. You can directly expose ObjectMapper object to your service layer and perform the deserialization.
If you want to shield the json serializing mechanism from your service layer then you can wrap ObjectMapper implementation into another class (like you have done in your code) but then, accept the class type as a method argument rather than a generic type.

Jackson filtering out fields without annotations

I was trying to filter out certain fields from serialization via SimpleBeanPropertyFilter using the following (simplified) code:
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter("test",
SimpleBeanPropertyFilter.filterOutAllExcept("data1"));
try {
String json = mapper.writer(filterProvider).writeValueAsString(new Data());
System.out.println(json); // output: {"data1":"value1","data2":"value2"}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
private static class Data {
public String data1 = "value1";
public String data2 = "value2";
}
Us I use SimpleBeanPropertyFilter.filterOutAllExcept("data1")); I was expecting that the created serialized Json string contains only {"data1":"value1"}, however I get {"data1":"value1","data2":"value2"}.
How to create a temporary writer that respects the specified filter (the ObjectMapper can not be re-configured in my case).
Note: Because of the usage scenario in my application I can only accept answers that do not use Jackson annotations.
If for some reason MixIns does not suit you. You can try this approach:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector(){
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
List<String> exclusions = Arrays.asList("field1", "field2");
return exclusions.contains(m.getName())|| super.hasIgnoreMarker(m);
}
});
You would normally annotate your Data class to have the filter applied:
#JsonFilter("test")
class Data {
You have specified that you can't use annotations on the class. You could use mix-ins to avoid annotating Data class.
#JsonFilter("test")
class DataMixIn {}
Mixins have to be specified on an ObjectMapper and you specify you don't want to reconfigure that. In such a case, you can always copy the ObjectMapper with its configuration and then modify the configuration of the copy. That will not affect the original ObjectMapper used elsewhere in your code. E.g.
ObjectMapper myMapper = mapper.copy();
myMapper.addMixIn(Data.class, DataMixIn.class);
And then write with the new ObjectMapper
String json = myMapper.writer(filterProvider).writeValueAsString(new Data());
System.out.println(json); // output: {"data1":"value1"}
The example of excluding properties by name:
public Class User {
private String name = "abc";
private Integer age = 1;
//getters
}
#JsonFilter("dynamicFilter")
public class DynamicMixIn {
}
User user = new User();
String[] propertiesToExclude = {"name"};
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Object.class, DynamicMixIn.class);
FilterProvider filterProvider = new SimpleFilterProvider()
.addFilter("dynamicFilter", SimpleBeanPropertyFilter.filterOutAllExcept(propertiesToExclude));
mapper.setFilterProvider(filterProvider);
mapper.writeValueAsString(user); // {"name":"abc"}
You can instead of DynamicMixIn create MixInByPropName
#JsonIgnoreProperties(value = {"age"})
public class MixInByPropName {
}
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Object.class, MixInByPropName.class);
mapper.writeValueAsString(user); // {"name":"abc"}
Note: If you want exclude property only for User you can change parameter Object.class of method addMixIn to User.class
Excluding properties by type you can create MixInByType
#JsonIgnoreType
public class MixInByType {
}
ObjectMapper mapper = new ObjectMapper()
.addMixIn(Integer.class, MixInByType.class);
mapper.writeValueAsString(user); // {"name":"abc"}
It seems you have to add an annotation which indicts which filter to use when doing the serialization to the bean class if you want the filter to work:
#JsonFilter("test")
public class Data {
public String data1 = "value1";
public String data2 = "value2";
}
EDIT
The OP has just added a note that just take the answer that not using a bean animation, then if the field you want to export is very less amount, you can just retrieve that data and build a Map of List yourself, there seems no other way to do that.
Map<String, Object> map = new HashMap<String, Object>();
map.put("data1", obj.getData1());
...
// do the serilization on the map object just created.
If you want to exclude specific field and kept the most field, maybe you could do that with reflect. Following is a method I have written to transfer a bean to a map you could change the code to meet your own needs:
protected Map<String, Object> transBean2Map(Object beanObj){
if(beanObj == null){
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(beanObj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
if (!key.equals("class")
&& !key.endsWith("Entity")
&& !key.endsWith("Entities")
&& !key.endsWith("LazyInitializer")
&& !key.equals("handler")) {
Method getter = property.getReadMethod();
if(key.endsWith("List")){
Annotation[] annotations = getter.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof javax.persistence.OneToMany){
if(((javax.persistence.OneToMany)annotation).fetch().equals(FetchType.EAGER)){
List entityList = (List) getter.invoke(beanObj);
List<Map<String, Object>> dataList = new ArrayList<>();
for(Object childEntity: entityList){
dataList.add(transBean2Map(childEntity));
}
map.put(key,dataList);
}
}
}
continue;
}
Object value = getter.invoke(beanObj);
map.put(key, value);
}
}
} catch (Exception e) {
Logger.getAnonymousLogger().log(Level.SEVERE,"transBean2Map Error " + e);
}
return map;
}
But I recommend you to use Google Gson as the JSON deserializer/serializer And the main reason is I hate dealing with exception stuff, it just messed up with the coding style.
And it's pretty easy to satisfy your need with taking advantage of the version control annotation on the bean class like this:
#Since(GifMiaoMacro.GSON_SENSITIVE) //mark the field as sensitive data and will not export to JSON
private boolean firstFrameStored; // won't export this field to JSON.
You can define the Macro whether to export or hide the field like this:
public static final double GSON_SENSITIVE = 2.0f;
public static final double GSON_INSENSITIVE = 1.0f;
By default, Gson will export all field that not annotated by #Since So you don't have to do anything if you do not care about the field and it just exports the field.
And if some field you are not want to export to json, ie sensitive info just add an annotation to the field. And generate json string with this:
private static Gson gsonInsensitive = new GsonBuilder()
.registerTypeAdapter(ObjectId.class,new ObjectIdSerializer()) // you can omit this line and the following line if you are not using mongodb
.registerTypeAdapter(ObjectId.class, new ObjectIdDeserializer()) //you can omit this
.setVersion(GifMiaoMacro.GSON_INSENSITIVE)
.disableHtmlEscaping()
.create();
public static String toInsensitiveJson(Object o){
return gsonInsensitive.toJson(o);
}
Then just use this:
String jsonStr = StringUtils.toInsensitiveJson(yourObj);
Since Gson is stateless, it's fine to use a static method to do your job, I have tried a lot of JSON serialize/deserialize framework with Java, but found Gson to be the sharp one both performance and handily.

Jackson deserialize json property into object

I need to deserialize the following json:
{
//...
"foo_id":1
//...
}
Into an object of class Foo with its id property set to the foo_id json property.
I need to do this within a custom deserializer.
What is the most easy way to accomplish this?
I was thinking to somehow "transform" the json to
{
//...
"foo_id":{
"id":1
}
//...
}
and then delegate this back to Jackson.
In this case, the object is of type Foo, but there are others which might not be of this class. Also, in this case, that json is a number, but I would like to support if it was a string as well.
So, I need a kind of generic way to do this, that's why I think delegating back to Jackson might be a good idea.
No annotations allowed. Suppose you're already writing the Deserializer for this property.
Take a look at this. Here is a code that I think might help you to get some ideas.
public class MyDeserializer extends JsonDeserializer< Message >
{
#Override
public Message deserialize( JsonParser jp, DeserializationContext arg1 ) throws IOException,
JsonProcessingException
{
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(jp);
Class<? extends Message> subClass = null;
Iterator<Entry<String, JsonNode>> elementsIterator = root.getFields();
while (elementsIterator.hasNext())
{
Entry<String, JsonNode> element = elementsIterator.next();
String name = element.getKey();
if ("foo_id".equals(name))
{
if(element.getValue().isInt())
subClass = FooInteger.Class;
break;
}
}
if (subClass == null) return null;
return mapper.readValue(root, subClass);
}
}
Have you considered use of mix-in annotations? With Jackson 2.2, you could also use converters to do two-step processing (#JsonDeserialize(converter=MyConverter.class).

Categories

Resources