Skip to content

Commit 538c656

Browse files
authored
Abstraction for JSON object mapper (#3319)
* Edited gson dependency as optional and add jackson dependency only for tests purpose * Created exception for config error purpose * Classes and interfaces created to gain code decoupling from json convertion implementation * Class is no longer needed * Interfaces created as contract for all redis command json operations to set an implementation of a json convertion by polymorphism * Changed json engine implementation to an interface to decouple code and make it interchangeable * Implemented contract to define a json engine encoding/decoding * classes created for json conversion with different json engines for testing purposes and junit tests for them * Removed import that is no longer used * Removed the optional statement for gson lib to not break the change * Just removed unused exception * Edited to use a plain jedis exception instead of a custom one * setting gson as default json parser automatically when none set * Removed unnecessary interface but kept setters for json serializer/deserializer engine configuration * Unused imports removed * Classes, interfaces, methods and other comment references renamed from word 'parser' to 'object mapper'. * Classes and interfaces moved to 'json' package because it's not need a separate package * Made some tweaks to the custom object mapper for jackson which throws a jedi's Exception instead of just printing stack trace. * Refactored method to get json object mapper reference using double checked lock approach as suggestion in review code * Removed unit test no longer occurs jedis exception after refactoring json object mapper instance in 'command object class' * Unused imports removed
1 parent 70179cc commit 538c656

File tree

12 files changed

+308
-55
lines changed

12 files changed

+308
-55
lines changed

pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,18 @@
103103
<version>3.12.4</version>
104104
<scope>test</scope>
105105
</dependency>
106+
<dependency>
107+
<groupId>com.fasterxml.jackson.core</groupId>
108+
<artifactId>jackson-databind</artifactId>
109+
<version>2.14.2</version>
110+
<scope>test</scope>
111+
</dependency>
112+
<dependency>
113+
<groupId>com.fasterxml.jackson.datatype</groupId>
114+
<artifactId>jackson-datatype-jsr310</artifactId>
115+
<version>2.14.2</version>
116+
<scope>test</scope>
117+
</dependency>
106118
</dependencies>
107119

108120
<distributionManagement>

src/main/java/redis/clients/jedis/CommandObjects.java

Lines changed: 76 additions & 39 deletions
Large diffs are not rendered by default.

src/main/java/redis/clients/jedis/GsonJson.java

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/main/java/redis/clients/jedis/MultiNodePipelineBase.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import redis.clients.jedis.json.JsonSetParams;
2424
import redis.clients.jedis.json.Path;
2525
import redis.clients.jedis.json.Path2;
26+
import redis.clients.jedis.json.JsonObjectMapper;
2627
import redis.clients.jedis.params.*;
2728
import redis.clients.jedis.providers.ConnectionProvider;
2829
import redis.clients.jedis.resps.*;
@@ -4300,4 +4301,8 @@ public Response<List<String>> graphProfile(String graphName, String query) {
43004301
public Response<Long> waitReplicas(int replicas, long timeout) {
43014302
return appendCommand(commandObjects.waitReplicas(replicas, timeout));
43024303
}
4304+
4305+
public void setJsonObjectMapper(JsonObjectMapper jsonObjectMapper) {
4306+
this.commandObjects.setJsonObjectMapper(jsonObjectMapper);
4307+
}
43034308
}

src/main/java/redis/clients/jedis/Pipeline.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import redis.clients.jedis.json.JsonSetParams;
2121
import redis.clients.jedis.json.Path;
2222
import redis.clients.jedis.json.Path2;
23+
import redis.clients.jedis.json.JsonObjectMapper;
2324
import redis.clients.jedis.params.*;
2425
import redis.clients.jedis.resps.*;
2526
import redis.clients.jedis.search.*;
@@ -4361,4 +4362,8 @@ public Response<Object> sendCommand(CommandArguments args) {
43614362
public <T> Response<T> executeCommand(CommandObject<T> command) {
43624363
return appendCommand(command);
43634364
}
4365+
4366+
public void setJsonObjectMapper(JsonObjectMapper jsonObjectMapper) {
4367+
this.commandObjects.setJsonObjectMapper(jsonObjectMapper);
4368+
}
43644369
}

src/main/java/redis/clients/jedis/TransactionBase.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import redis.clients.jedis.json.JsonSetParams;
2727
import redis.clients.jedis.json.Path;
2828
import redis.clients.jedis.json.Path2;
29+
import redis.clients.jedis.json.JsonObjectMapper;
2930
import redis.clients.jedis.params.*;
3031
import redis.clients.jedis.resps.*;
3132
import redis.clients.jedis.search.*;
@@ -4376,4 +4377,8 @@ public Response<Object> sendCommand(CommandArguments args) {
43764377
public <T> Response<T> executeCommand(CommandObject<T> command) {
43774378
return appendCommand(command);
43784379
}
4380+
4381+
public void setJsonObjectMapper(JsonObjectMapper jsonObjectMapper) {
4382+
this.commandObjects.setJsonObjectMapper(jsonObjectMapper);
4383+
}
43794384
}

src/main/java/redis/clients/jedis/UnifiedJedis.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import redis.clients.jedis.json.JsonSetParams;
2424
import redis.clients.jedis.json.Path;
2525
import redis.clients.jedis.json.Path2;
26+
import redis.clients.jedis.json.JsonObjectMapper;
2627
import redis.clients.jedis.params.*;
2728
import redis.clients.jedis.providers.*;
2829
import redis.clients.jedis.resps.*;
@@ -4643,4 +4644,8 @@ public Object sendBlockingCommand(String sampleKey, ProtocolCommand cmd, String.
46434644
public Object executeCommand(CommandArguments args) {
46444645
return executeCommand(new CommandObject<>(args, BuilderFactory.RAW_OBJECT));
46454646
}
4647+
4648+
public void setJsonObjectMapper(JsonObjectMapper jsonObjectMapper) {
4649+
this.commandObjects.setJsonObjectMapper(jsonObjectMapper);
4650+
}
46464651
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package redis.clients.jedis.json;
2+
3+
import com.google.gson.Gson;
4+
5+
/**
6+
* Use the default {@link Gson} configuration for serialization and deserialization JSON
7+
* operations.
8+
* <p>When none is explicitly set, this will be set.</p>
9+
* @see JsonObjectMapper Create a custom JSON serializer/deserializer
10+
*/
11+
public class DefaultGsonObjectMapper implements JsonObjectMapper {
12+
/**
13+
* Instance of Gson object with default gson configuration.
14+
*/
15+
private final Gson gson = new Gson();
16+
17+
@Override
18+
public <T> T fromJson(String value, Class<T> valueType) {
19+
return gson.fromJson(value, valueType);
20+
}
21+
22+
@Override
23+
public String toJson(Object value) {
24+
return gson.toJson(value);
25+
}
26+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package redis.clients.jedis.json;
2+
3+
/**
4+
* Represents the ability of serialize an object to JSON format string and deserialize it to the
5+
* typed object.
6+
* @see DefaultGsonObjectMapper Default implementation for <em>JSON serializer/deserializer</em>
7+
* engine with com.google.gson.Gson
8+
*/
9+
public interface JsonObjectMapper {
10+
/**
11+
* Perform deserialization from JSON format string to the given type object as argument.
12+
* @param value the JSON format
13+
* @param valueType the object type to convert
14+
* @param <T> the type object to convert
15+
* @return the instance of an object to the type given argument
16+
*/
17+
<T> T fromJson(String value, Class<T> valueType);
18+
19+
/**
20+
* Perform serialization from object to JSON format string.
21+
* @param value the object to convert
22+
* @return the JSON format string
23+
*/
24+
String toJson(Object value);
25+
}

src/test/java/redis/clients/jedis/modules/json/JsonObjects.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package redis.clients.jedis.modules.json;
22

3+
import java.time.Instant;
34
import java.util.Objects;
45

56
public class JsonObjects {
@@ -99,4 +100,19 @@ public boolean equals(Object o) {
99100
&& Objects.equals(baz, other.baz);
100101
}
101102
}
103+
104+
public static class Person {
105+
private final String id;
106+
private final Instant created;
107+
public Person(String id, Instant created) {
108+
this.id = id;
109+
this.created = created;
110+
}
111+
public String getId() {
112+
return id;
113+
}
114+
public Instant getCreated() {
115+
return created;
116+
}
117+
}
102118
}

src/test/java/redis/clients/jedis/modules/json/RedisJsonV1Test.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import com.google.gson.Gson;
88
import com.google.gson.JsonArray;
99
import com.google.gson.JsonObject;
10+
11+
import java.time.Instant;
1012
import java.util.Arrays;
1113
import java.util.Collections;
1214
import java.util.List;
@@ -17,6 +19,7 @@
1719
import redis.clients.jedis.json.JsonSetParams;
1820
import redis.clients.jedis.json.Path;
1921
import redis.clients.jedis.modules.RedisModuleCommandsTestBase;
22+
import redis.clients.jedis.util.JsonObjectMapperTestUtil;
2023

2124
public class RedisJsonV1Test extends RedisModuleCommandsTestBase {
2225

@@ -490,4 +493,42 @@ public void resp() {
490493
assertEquals("2.5", arr.get(3));
491494
assertEquals("true", arr.get(4));
492495
}
496+
497+
@Test
498+
public void testJsonGsonParser() {
499+
Person person = new Person("foo", Instant.now());
500+
501+
// setting the custom json gson parser
502+
client.setJsonObjectMapper(JsonObjectMapperTestUtil.getCustomGsonObjectMapper());
503+
504+
client.jsonSet(person.getId(), ROOT_PATH, person);
505+
506+
String valueExpected = client.jsonGet(person.getId(), String.class, Path.of(".created"));
507+
assertEquals(valueExpected, person.getCreated().toString());
508+
}
509+
510+
@Test
511+
public void testDefaultJsonGsonParserStringsMustBeDifferent() {
512+
Person person = new Person("foo", Instant.now());
513+
514+
// using the default json gson parser which is automatically configured
515+
516+
client.jsonSet(person.getId(), ROOT_PATH, person);
517+
518+
Object valueExpected = client.jsonGet(person.getId(), Path.of(".created"));
519+
assertNotEquals(valueExpected, person.getCreated().toString());
520+
}
521+
522+
@Test
523+
public void testJsonJacksonParser() {
524+
Person person = new Person("foo", Instant.now());
525+
526+
// setting the custom json jackson parser
527+
client.setJsonObjectMapper(JsonObjectMapperTestUtil.getCustomJacksonObjectMapper());
528+
529+
client.jsonSet(person.getId(), ROOT_PATH, person);
530+
531+
String valueExpected = client.jsonGet(person.getId(), String.class, Path.of(".created"));
532+
assertEquals(valueExpected, person.getCreated().toString());
533+
}
493534
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package redis.clients.jedis.util;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.fasterxml.jackson.databind.SerializationFeature;
6+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
7+
import com.google.gson.*;
8+
import redis.clients.jedis.exceptions.JedisException;
9+
import redis.clients.jedis.json.JsonObjectMapper;
10+
11+
import java.lang.reflect.Type;
12+
import java.time.Instant;
13+
import java.time.format.DateTimeFormatter;
14+
import java.time.temporal.TemporalAccessor;
15+
16+
public class JsonObjectMapperTestUtil {
17+
public static CustomJacksonObjectMapper getCustomJacksonObjectMapper() {
18+
ObjectMapper om = new ObjectMapper();
19+
om.registerModule(new JavaTimeModule());
20+
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
21+
return new CustomJacksonObjectMapper(om);
22+
}
23+
24+
public static CustomGsonObjectMapper getCustomGsonObjectMapper() {
25+
final class InstantAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
26+
DateTimeFormatter format = DateTimeFormatter.ISO_INSTANT;
27+
28+
@Override
29+
public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
30+
throws JsonParseException {
31+
JsonPrimitive primitive = json.getAsJsonPrimitive();
32+
if (!primitive.isJsonNull()) {
33+
String asString = primitive.getAsString();
34+
TemporalAccessor temporalAccessor = format.parse(asString);
35+
return Instant.from(temporalAccessor);
36+
}
37+
return null;
38+
}
39+
40+
@Override
41+
public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
42+
return new JsonPrimitive(format.format(src));
43+
}
44+
}
45+
return new CustomGsonObjectMapper(
46+
new GsonBuilder().registerTypeAdapter(Instant.class, new InstantAdapter()).create());
47+
}
48+
49+
public static class CustomJacksonObjectMapper implements JsonObjectMapper {
50+
private final ObjectMapper om;
51+
52+
CustomJacksonObjectMapper(ObjectMapper om) {
53+
this.om = om;
54+
}
55+
56+
@Override
57+
public <T> T fromJson(String value, Class<T> valueType) {
58+
try {
59+
return om.readValue(value, valueType);
60+
} catch (JsonProcessingException e) {
61+
throw new JedisException(e);
62+
}
63+
}
64+
65+
@Override
66+
public String toJson(Object value) {
67+
try {
68+
return om.writeValueAsString(value);
69+
} catch (JsonProcessingException e) {
70+
throw new JedisException(e);
71+
}
72+
}
73+
}
74+
75+
public static class CustomGsonObjectMapper implements JsonObjectMapper {
76+
private final Gson gson;
77+
78+
public CustomGsonObjectMapper(Gson gson) {
79+
this.gson = gson;
80+
}
81+
82+
@Override
83+
public <T> T fromJson(String value, Class<T> valueType) {
84+
return gson.fromJson(value, valueType);
85+
}
86+
87+
@Override
88+
public String toJson(Object value) {
89+
return gson.toJson(value);
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)