how to convert list fields to map in java - 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.

Related

Can not cast deserialized json array with jackson

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

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);

How to serialize Java Instant type using redis

I'm using spring boot 2.0.3 and
spring-boot-starter-data-redis.
Also using jackson-datatype-jsr310.
I want to store Object into redis.
the object(MyObj):
String text;
Instant instant;
Here's my code:
#Test
public void test() {
ListOperations<String, MyObj> listOps = redisTemplate.opsForList();
MyObj o1 = new MyObj();
o1.setText("foo");
o1.setInstant(Instant.now());
listOps.leftPush("foo", o1);
MyObj o2 = new MyObj();
o2.setText("bar");
o2.setInstant(Instant.now());
listOps.leftPush("foo", o2);
List<MyObj> list = listOps.range("foo", 0, -1);
for (MyObj o : list) {
System.out.println(o.getText());
System.out.println(o.getInstant());
}
}
in my RedisConfig:
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
But when I'm pushing into redis, the error occurs below:
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of java.time.Instant (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
How to serialize java instant type with Redis?
Any opinion would be appreciated.
While this is quite an old post, I ran into this problem recently and did the lazy man search and found this before deciding to read the class file. I found that you can easily override the default ObjectMapper with a custom one. Use the setObjectMapper(ObjectMapper objectMapper) method on the Serializer to override the default.
// Taken from Jackson library
public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private final JavaType javaType;
private ObjectMapper objectMapper = new ObjectMapper();
// truncated
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
}
You just need to create an ObjectMapper with the JavaTime.Module registered like below
public static ObjectMapper dateMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
Jackson2JsonRedisSerializer<MyObj> valueSerializer = new
Jackson2JsonRedisSerializer<>(MyObj.class);
valueSerializer.setObjectMapper(dateMapper());
}
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) turns of default behavior.

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.

How to use JSONDeserializer to deserialize this string?

I have string like follows:
{"226167":"myshow","3193":"yourshow"}
How can I use JSONDeserializer to extract (226167,3193) from the above string object?
I probably want to have a list (226167,3193,...) from the above string. I am using flexjason 1.9.2 and it doesn't have jsonObject class.
Use the jackson JSON library's ObjectMapper class to deserialize into a Map and then get the keySet():
Required Import:
import org.codehaus.jackson.map.ObjectMapper;
Example:
public void readValueAsMap() throws Exception
{
String value = "{\"226167\":\"myshow\",\"3193\":\"yourshow\"}";
ObjectMapper mapper = new ObjectMapper();
Map<String,String> valueAsMap = mapper.readValue(value, Map.class);
Collection<String> values = valueAsMap.keySet();
assertTrue(values.contains("226167"));
assertTrue(values.contains("3193"));
}

Categories

Resources