Skip to content

Commit a2831be

Browse files
christophstroblmp911de
authored andcommitted
DATAREDIS-1001 - Fix java.util.Date hash mapping when flattening out entries.
The AsArrayTypeDeserializer causes date values to be rendered as an array like [java.util.Date, 1561543964015] an therefore is 1. skipped when flattening and 2. if not skipped cannot be read back. We now register an error handler along with a custom deserializer aware of the used date format to write and read date values without having to store an array but just the date msec. Original pull request: spring-projects#467.
1 parent b72bf41 commit a2831be

File tree

3 files changed

+132
-12
lines changed

3 files changed

+132
-12
lines changed

src/main/java/org/springframework/data/redis/hash/Jackson2HashMapper.java

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
package org.springframework.data.redis.hash;
1717

1818
import java.io.IOException;
19+
import java.text.ParseException;
1920
import java.util.ArrayList;
21+
import java.util.Date;
2022
import java.util.HashMap;
2123
import java.util.Iterator;
2224
import java.util.LinkedHashMap;
@@ -29,15 +31,28 @@
2931
import org.springframework.data.mapping.MappingException;
3032
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
3133
import org.springframework.util.Assert;
34+
import org.springframework.util.NumberUtils;
3235
import org.springframework.util.StringUtils;
3336

3437
import com.fasterxml.jackson.annotation.JsonInclude.Include;
3538
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
39+
import com.fasterxml.jackson.core.JsonParser;
40+
import com.fasterxml.jackson.databind.BeanDescription;
41+
import com.fasterxml.jackson.databind.DeserializationConfig;
42+
import com.fasterxml.jackson.databind.DeserializationContext;
3643
import com.fasterxml.jackson.databind.DeserializationFeature;
44+
import com.fasterxml.jackson.databind.JavaType;
45+
import com.fasterxml.jackson.databind.JsonDeserializer;
3746
import com.fasterxml.jackson.databind.JsonNode;
3847
import com.fasterxml.jackson.databind.ObjectMapper;
3948
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
4049
import com.fasterxml.jackson.databind.SerializationFeature;
50+
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
51+
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
52+
import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer;
53+
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
54+
import com.fasterxml.jackson.databind.module.SimpleModule;
55+
import com.fasterxml.jackson.databind.type.TypeFactory;
4156

4257
/**
4358
* {@link ObjectMapper} based {@link HashMapper} implementation that allows flattening. Given an entity {@code Person}
@@ -65,19 +80,56 @@
6580
*
6681
* <strong>Normal</strong>
6782
* <table>
68-
* <tr><th>Hash field</th><th>Value<th></tr>
69-
* <tr><td>firstname</td><td>Jon<td></tr>
70-
* <tr><td>lastname</td><td>Snow<td></tr>
71-
* <tr><td>address</td><td>{ "city" : "Castle Black", "country" : "The North" }<td></tr>
83+
* <tr>
84+
* <th>Hash field</th>
85+
* <th>Value
86+
* <th>
87+
* </tr>
88+
* <tr>
89+
* <td>firstname</td>
90+
* <td>Jon
91+
* <td>
92+
* </tr>
93+
* <tr>
94+
* <td>lastname</td>
95+
* <td>Snow
96+
* <td>
97+
* </tr>
98+
* <tr>
99+
* <td>address</td>
100+
* <td>{ "city" : "Castle Black", "country" : "The North" }
101+
* <td>
102+
* </tr>
72103
* </table>
73104
* <br />
74105
* <strong>Flat</strong>:
75106
* <table>
76-
* <tr><th>Hash field</th><th>Value<th></tr>
77-
*   <tr><td>firstname</td><td>Jon<td></tr>
78-
* <tr><td>lastname</td><td>Snow<td></tr>
79-
* <tr><td>address.city</td><td>Castle Black<td></tr>
80-
* <tr><td>address.country</td><td>The North<td></tr>
107+
* <tr>
108+
* <th>Hash field</th>
109+
* <th>Value
110+
* <th>
111+
* </tr>
112+
*  
113+
* <tr>
114+
* <td>firstname</td>
115+
* <td>Jon
116+
* <td>
117+
* </tr>
118+
* <tr>
119+
* <td>lastname</td>
120+
* <td>Snow
121+
* <td>
122+
* </tr>
123+
* <tr>
124+
* <td>address.city</td>
125+
* <td>Castle Black
126+
* <td>
127+
* </tr>
128+
* <tr>
129+
* <td>address.country</td>
130+
* <td>The North
131+
* <td>
132+
* </tr>
81133
* </table>
82134
*
83135
* @author Christoph Strobl
@@ -103,6 +155,50 @@ public Jackson2HashMapper(boolean flatten) {
103155
typingMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
104156
typingMapper.setSerializationInclusion(Include.NON_NULL);
105157
typingMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
158+
159+
typingMapper.addHandler(new DeserializationProblemHandler() {
160+
@Override
161+
public JavaType handleMissingTypeId(DeserializationContext ctxt, JavaType baseType, TypeIdResolver idResolver,
162+
String failureMsg) {
163+
return TypeFactory.defaultInstance().constructSimpleType(java.util.Date.class, new JavaType[] {});
164+
}
165+
});
166+
167+
SimpleModule module = new SimpleModule();
168+
module.setDeserializerModifier(new BeanDeserializerModifier() {
169+
@Override
170+
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc,
171+
JsonDeserializer<?> deserializer) {
172+
173+
if (beanDesc.getBeanClass().equals(java.util.Date.class)) {
174+
return new JsonDeserializer<Object>() {
175+
176+
JsonDeserializer<?> delegate = new UntypedObjectDeserializer(null, null);
177+
178+
@Override
179+
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
180+
181+
Object val = delegate.deserialize(p, ctxt);
182+
183+
if (val instanceof Date) {
184+
return val;
185+
}
186+
187+
try {
188+
return ctxt.getConfig().getDateFormat().parse(val.toString());
189+
} catch (ParseException e) {
190+
e.printStackTrace();
191+
return new Date(NumberUtils.parseNumber(val.toString(), Long.class));
192+
}
193+
}
194+
};
195+
}
196+
197+
return deserializer;
198+
}
199+
});
200+
201+
typingMapper.registerModule(module);
106202
}
107203

108204
/**
@@ -242,6 +338,7 @@ private void flattenElement(String propertyPrefix, Object source, Map<String, Ob
242338
}
243339

244340
JsonNode element = (JsonNode) source;
341+
245342
if (element.isArray()) {
246343

247344
Iterator<JsonNode> nodes = element.elements();
@@ -250,7 +347,13 @@ private void flattenElement(String propertyPrefix, Object source, Map<String, Ob
250347

251348
JsonNode cur = nodes.next();
252349
if (cur.isArray()) {
253-
this.falttenCollection(propertyPrefix, cur.elements(), resultMap);
350+
this.flattenCollection(propertyPrefix, cur.elements(), resultMap);
351+
} else {
352+
353+
if (cur.asText().equals("java.util.Date")) {
354+
resultMap.put(propertyPrefix, nodes.next().asText());
355+
break;
356+
}
254357
}
255358
}
256359

@@ -261,7 +364,7 @@ private void flattenElement(String propertyPrefix, Object source, Map<String, Ob
261364
}
262365
}
263366

264-
private void falttenCollection(String propertyPrefix, Iterator<JsonNode> list, Map<String, Object> resultMap) {
367+
private void flattenCollection(String propertyPrefix, Iterator<JsonNode> list, Map<String, Object> resultMap) {
265368

266369
int counter = 0;
267370
while (list.hasNext()) {

src/test/java/org/springframework/data/redis/mapping/Jackson2HashMapperTests.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,4 @@ public void shouldWriteReadHashCorrectly() {
9797
Person result = (Person) mapper.fromHash(template.<String, Object> opsForHash().entries("JON-SNOW"));
9898
Assert.assertThat(result, Is.is(jon));
9999
}
100-
101100
}

src/test/java/org/springframework/data/redis/mapping/Jackson2HashMapperUnitTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
2222
import java.util.Collection;
23+
import java.util.Date;
2324
import java.util.LinkedHashMap;
2425
import java.util.List;
2526
import java.util.Map;
@@ -161,6 +162,16 @@ public void nestedStuff() {
161162
assertBackAndForwardMapping(outer);
162163
}
163164

165+
@Test // DATAREDIS-1001
166+
public void dateValueShouldBeTreatedCorrectly() {
167+
168+
WithDate source = new WithDate();
169+
source.string = "id-1";
170+
source.date = new Date(1561543964015L);
171+
172+
assertBackAndForwardMapping(source);
173+
}
174+
164175
@Data
165176
public static class WithList {
166177
List<String> strings;
@@ -174,4 +185,11 @@ public static class WithMap {
174185
Map<String, Object> objects;
175186
Map<String, Person> persons;
176187
}
188+
189+
@Data
190+
static class WithDate {
191+
192+
private String string;
193+
private Date date;
194+
}
177195
}

0 commit comments

Comments
 (0)