I am using Spring Data Redis in order to cache some data using #Cacheable. I have multiple types of objects that need to be cached and I need the data from Redis to be in JSON format. I know that, by default, the serializer used is JdkSerializationRedisSerializer, but with is the cached data is not human readable.
I order to save the data in JSON format I wanted to use GenericJackson2JsonRedisSerializer and I've created a custom ObjectMapper too:
public RedisTemplate<Object, Object> redisTemplate (RedisConnectionFactory cf) {
ObjectMapper objectMapper = new Jackson2ObjectMapperBuilder().failOnEmptyBeans(false)
.failOnUnknownProperties(false)
.indentOutput(false)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.modules(
// Optional
new Jdk8Module(),
// Dates/Times
new JavaTimeModule()
)
.featuresToDisable(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS,
SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS
).build();
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(cf);
redisTemplate.setKeySerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return redisTemplate;
Using this RedisTemplate doesn't work and I always get back this error:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to <some class>
As I understood, when deserializing, Jackson doesn't know the type of the specific object since it's Object and creates a LinkedHashMap to hold the data. Maybe I am wrong with this, but how can I achieve saving the cached data as JSON for multiple types of objects with #Cacheble?
You are right, you can achieve saving multiple types with GenericJackson2JsonRedisSerializer. But in your example, you provide custom ObjectMapper to GenericJackson2JsonRedisSerializer and GenericJackson2JsonRedisSerializer doesn't configure Jackson's default typing for you (you can check default constructor logic in GenericJackson2JsonRedisSerializer). You have to tune it on your own and add this to your ObjectMapper
objectMapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
This will include type information to JSON. Like this
{
"#class": "com.example.Foo",
"field": "bar"
}
Since 2.10 ObjectMapper better to use
activateDefaultTyping(mapper.polymorphicTypeValidator, ObjectMapper.DefaultTyping.NON_FINAL)
Related
I have some json in a hashmap<string, object> and i’m using ObjectMapper to map it json. I need to change the field names of pretty much all of the values.
ex:
change this
{ “field1”:”abc”, “field2”: “xyz”}
to this
{“f1”:”abc”, “f2”:”xyz”}
I’ve read about using Jackson and using annotation #JsonProperty, but in my case it’s just not feasible because i need to change the names of atleast 20+ fields. What’s the best way to do this?
Why can’t you change ur inout source? Such large change is unnecessary and taxing
If you are parsing this Json with ObjectMapper, after getting intermediate DTO you can apply Mixin and export into String and then convert to required DTO.
Convert String to OriginalDTO with field1
Set Jackson mixin to object mapper
ObjectMapper mapper = new ObjectMapper();
#Getter
#Setter
public abstract class FinalMixin {
#JsonProperty("f1")
private String field1;
}
mapper.addMixInAnnotations(OriginalDTO.class, FinalMixin.class);
Convert DTO that you get in step 1 into String
Convert result String into FinalDTO
Given class:
#Data
class Widget {
String name;
int price;
}
And the following yaml file:
Widget1:
price: 5
Widget2:
price: 6
Widget3:
price: 7
Using Jackson I want to deserialize this into a Map<String, Widget> where the widget name field is set to the corresponding map key. The following snippet works but has the downside of preventing use of immutable object types such as lombok #Value. Another inelegant solution I considered was creating a separate immutable WidgetWithName class that is constructed after jackson deserialization. If someone can propose a better approach, that would be of interest.
Map<String, Widget> getWidgets(String yaml) throws Exception {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
TypeFactory typeFactory = mapper.getTypeFactory();
MapType mapType = typeFactory.constructMapType(HashMap.class, String.class, Widget.class);
map = mapper.readValue(yaml, mapType);
map.forEach((k, v) -> v.setName(k); // so much for immutability
return map
}
Jackson will not help you because it is a generalization over YAML and JSON (and XML or so I heard) and therefore provides less functionality than directly using SnakeYAML.
With SnakeYAML, you could theoretically implement this with custom constructors, but it would need a lot of knowledge about the deserialization process because YAML is not designed to merge nodes on different levels into one value.
Frankly, having a separate class is by far the simplest and best maintainable solution. A separate class named WidgetBuilder, with only the price field and a function Widget finish(String name) would allow you to
return map.entrySet().stream().map((k, v) -> v.finish(k)).collect(
Collectors.toMap(Widget::getName, Function.identity()));
which seems elegant enough. (You can collect to a list instead if you don't need the map anymore.)
I want to map between simple object to protobuff using object mapper
when i tried this it cause an exception
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.convertValue(enterprise, EnterpriseMessage.Enterprise.class);
Exception message was: cannot find a (map) key deserializer for type simple type
In my opinion objectmapper is not the best option to map protos
since objectmapper is used to map JSON into POJO's and vice versa.
My recomendation for this purpose is using mapstruct which provides
a wide functionality to map java beans. Specially between protos and POJO's.
Just by creating an interface mapper for the class you want to map the framework
generates the implementacion.
I write you an example that you can follow.
import org.mapstruct.Mapper;
#Mapper
public interface EnterpriseProtoMapper {
EnterpriseMessage.Enterprise toProto(Enterprise enterprise);
}
For further information, you can check mapStruct's documentation in this link:
MapStruct 1.3.1.Final Reference Guide
I'm looking to replace Jackson deserialization with Boon to test the differences in deserialization speeds. I am reading JSON from a file (which can be in the millions of lines long), consisting of multiple blocks that will each represent a POJO instance (MyPojo.java)and storing these instances in a Collection. I also have a custom deserializer that will omit the creation of certain POJOs. At the minute I have the following in Jackson:
public Collection<MyPojo> load()
{
ObjectMapper mapper = new ObjectMapper().registerModule(new MyCustomDeserializer());
return mapper.readValue(jsonFile, new TypeReference<Collection<MyPojo>>(){});
}
I know that the Boon API mimics Jacksons so I tried:
ObjectMapper boonMapper = JsonFactory.create();
return boonMapper.readValue(jsonFile, new TypeReference<Collection<MyPojo>>(){});
...but it doesn't seem to like this, it can't find the method that accepts these types.
Forgetting the registering the custom deserializer for now (that'll be my next problem), is this type of deserialization, straight to a Collection, supported in Boon?
Do the following;
return boonMapper.readValue(jsonFile, List.class, MyPojo.class);
I'm trying to serialize/deserialize a group of objects including sub-classes with jackson. I am using the JsonTypeInfo and JsonSubTypes annotations. It works fine working with DynamoDB but I'm also trying to cache using Redis (via spring's redis components). It writes to redis fine, but am getting the following error when trying to retrieve from redis :
Could not resolve type id 'LinkedTreeMap' into a subtype of [simple type, myBaseClass] : known type ids = [myBaseClass, sub-class1, sub-class1...]
... through reference chain....->[collection of MyBaseClass]->java.util.ArrayList[0]...
I think the LinkedTreeMap is in the gson library. We have a pretty large maven project and gson is in there due to other library dependencies, but I don't see how or why it's being pulled into this usage. It looks like it's deriving this class based on the structure of the json. The Json it's trying to deserialize looks like it is a list instead of a map.
So where is this LinkedTreeMap coming from?
We are also using the following ObjectMapper for redis :
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd\'T\'HH:mm:ss.SSSZ"));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.registerModule(new JodaModule());
We are also using a custom serializer which implements RedisSerializer. You'd think this might be the first place to look for issues like this, but it is pretty minimal, and seems to be necessary when you autowire RedisTemplate with Spring #Bean.