diff --git a/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java b/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java
new file mode 100644
index 0000000000..1d6aab05fd
--- /dev/null
+++ b/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.redis.serializer;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ser.SerializerFactory;
+
+/**
+ * {@link RedisSerializer} that can read and write JSON for arbitrary input types at the expense of additional type
+ * information stored with with value by using Jackson's and Jackson Databind {@link ObjectMapper}.
+ *
+ * This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
+ * Note: Null objects are serialized as empty arrays and vice versa.
+ *
+ * The serialized form consists of:
+ *
+ * Payload type name length (int, 4 bytes)
+ * Payload type name in bytes encoded with UTF-8
+ * Payload data bytes
+ *
+ *
+ * @author Thomas Darimont
+ * @since 1.6
+ */
+public class GenericJackson2JsonRedisSerializer implements RedisSerializer {
+
+ public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * Creates a new {@link GenericJackson2JsonRedisSerializer}.
+ */
+ public GenericJackson2JsonRedisSerializer() {}
+
+ /* (non-Javadoc)
+ * @see org.springframework.data.redis.serializer.RedisSerializer#deserialize(byte[])
+ */
+ @Override
+ public Object deserialize(byte[] bytes) throws SerializationException {
+
+ if (SerializationUtils.isEmpty(bytes)) {
+ return null;
+ }
+
+ try {
+
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+
+ int typeLength = buffer.getInt();
+ byte[] typeBytes = new byte[typeLength];
+ buffer.get(typeBytes);
+
+ // resolve valuetype first to allow early exit on unresolveable types
+ Class> valueType = resolveTypeFrom(typeBytes);
+
+ byte[] valueBytes = new byte[buffer.remaining()];
+ buffer.get(valueBytes);
+
+ return this.objectMapper.readValue(valueBytes, 0, valueBytes.length, valueType);
+ } catch (Exception ex) {
+ throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
+ }
+ }
+
+ /**
+ * Resolves the {@link Class} to be used from the given {@code typeBytes}.
+ *
+ * This can be overridden to cache type lookups.
+ *
+ * @param typeBytes
+ * @return
+ * @throws ClassNotFoundException
+ */
+ protected Class> resolveTypeFrom(byte[] typeBytes) throws ClassNotFoundException {
+
+ String className = new String(typeBytes, DEFAULT_CHARSET);
+ return ClassUtils.resolveClassName(className, getClass().getClassLoader());
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.data.redis.serializer.RedisSerializer#serialize(java.lang.Object)
+ */
+ @Override
+ public byte[] serialize(Object value) throws SerializationException {
+
+ if (value == null) {
+ return SerializationUtils.EMPTY_ARRAY;
+ }
+
+ try {
+
+ byte[] valueBytes = this.objectMapper.writeValueAsBytes(value);
+ byte[] typeBytes = convertTypeToBytes(value.getClass());
+
+ ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + typeBytes.length + valueBytes.length);
+ buffer.putInt(typeBytes.length);
+ buffer.put(typeBytes);
+ buffer.put(valueBytes);
+
+ return buffer.array();
+ } catch (Exception ex) {
+ throw new SerializationException("Could not write JSON: " + ex.getMessage(), ex);
+ }
+ }
+
+ /**
+ * Converts a given {@code Class} type to a {@code byte[]} representation.
+ *
+ * Note that the representation must be readable from {@link #resolveTypeFrom(byte[])}.
+ *
+ * @param type
+ * @return
+ */
+ protected byte[] convertTypeToBytes(Class> type) {
+ return type.getName().getBytes(DEFAULT_CHARSET);
+ }
+
+ /**
+ * Sets the {@code ObjectMapper} for this view. If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper}
+ * is used.
+ *
+ * Setting a custom-configured {@code ObjectMapper} is one way to take further control of the JSON serialization
+ * process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for
+ * specific types. The other option for refining the serialization process is to use Jackson's provided annotations on
+ * the types to be serialized, in which case a custom-configured ObjectMapper is unnecessary.
+ */
+ public void setObjectMapper(ObjectMapper objectMapper) {
+
+ Assert.notNull(objectMapper, "'objectMapper' must not be null");
+ this.objectMapper = objectMapper;
+ }
+}
diff --git a/src/test/java/org/springframework/data/redis/AddressObjectFactory.java b/src/test/java/org/springframework/data/redis/AddressObjectFactory.java
new file mode 100644
index 0000000000..f890892794
--- /dev/null
+++ b/src/test/java/org/springframework/data/redis/AddressObjectFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.redis;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author Thomas Darimont
+ */
+public class AddressObjectFactory implements ObjectFactory
{
+
+ private final AtomicInteger counter = new AtomicInteger();
+
+ @Override
+ public Address instance() {
+ return new Address("Street " + counter.intValue(), counter.intValue());
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerTests.java b/src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerTests.java
new file mode 100644
index 0000000000..cde3704464
--- /dev/null
+++ b/src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerTests.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.redis.serializer;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.hamcrest.core.Is;
+import org.hamcrest.core.IsNull;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.data.redis.AddressObjectFactory;
+import org.springframework.data.redis.ObjectFactory;
+import org.springframework.data.redis.Person;
+import org.springframework.data.redis.PersonObjectFactory;
+
+/**
+ * @author Thomas Darimont
+ */
+public class GenericJackson2JsonRedisSerializerTests {
+
+ private GenericJackson2JsonRedisSerializer serializer;
+
+ private List> objectFactories;
+
+ @Before
+ @SuppressWarnings("unchecked")
+ public void setUp() {
+
+ this.serializer = new GenericJackson2JsonRedisSerializer();
+ this.objectFactories = Arrays.> asList(new PersonObjectFactory(),
+ new AddressObjectFactory());
+ }
+
+ /**
+ * @see DATAREDIS-390
+ */
+ @Test
+ public void beAbleToSerializeMultipleTypes() throws Exception {
+
+ for (ObjectFactory extends Object> factory : objectFactories) {
+ Object instance = factory.instance();
+ assertEquals(instance, serializer.deserialize(serializer.serialize(instance)));
+ }
+ }
+
+ @Test
+ public void testJackson2JsonSerializerShouldReturnEmptyByteArrayWhenSerializingNull() {
+ assertThat(serializer.serialize(null), Is.is(new byte[0]));
+ }
+
+ @Test
+ public void testJackson2JsonSerializerShouldReturnNullWhenDerserializingEmtyByteArray() {
+ assertThat(serializer.deserialize(new byte[0]), IsNull.nullValue());
+ }
+
+ @Test(expected = SerializationException.class)
+ public void testJackson2JsonSerilizerShouldThrowExceptionWhenDeserializingInvalidByteArray() {
+
+ Person person = new PersonObjectFactory().instance();
+ byte[] serializedValue = serializer.serialize(person);
+ Arrays.sort(serializedValue); // corrupt serialization result
+
+ serializer.deserialize(serializedValue);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testJackson2JsonSerilizerThrowsExceptionWhenSettingNullObjectMapper() {
+ serializer.setObjectMapper(null);
+ }
+
+}
diff --git a/src/test/java/org/springframework/data/redis/serializer/SimpleRedisSerializerTests.java b/src/test/java/org/springframework/data/redis/serializer/SimpleRedisSerializerTests.java
index 8b80cc7cfe..b76596b31e 100644
--- a/src/test/java/org/springframework/data/redis/serializer/SimpleRedisSerializerTests.java
+++ b/src/test/java/org/springframework/data/redis/serializer/SimpleRedisSerializerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2013 the original author or authors.
+ * Copyright 2011-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
/**
* @author Jennifer Hickey
+ * @author Thomas Darimont
*/
public class SimpleRedisSerializerTests {
@@ -157,4 +158,27 @@ public void testJsonSerializer() throws Exception {
assertEquals(p1, serializer.deserialize(serializer.serialize(p1)));
}
+ @Test
+ public void testJackson2JsonSerializer() throws Exception {
+
+ Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Person.class);
+ String value = UUID.randomUUID().toString();
+ Person p1 = new Person(value, value, 1, new Address(value, 2));
+ assertEquals(p1, serializer.deserialize(serializer.serialize(p1)));
+ assertEquals(p1, serializer.deserialize(serializer.serialize(p1)));
+ }
+
+ /**
+ * @see DATAREDIS-390
+ */
+ @Test
+ public void testGenericJackson2JsonSerializer() throws Exception {
+
+ GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
+ String value = UUID.randomUUID().toString();
+ Person p1 = new Person(value, value, 1, new Address(value, 2));
+ assertEquals(p1, serializer.deserialize(serializer.serialize(p1)));
+ assertEquals(p1, serializer.deserialize(serializer.serialize(p1)));
+ }
+
}