Skip to content

Commit e1bba35

Browse files
authored
centralize the serialization/deserialization logic (#36)
* add JSON handling * add GsonJsonHandler * add JacksonJsonHandler * add JsonB Handler * add Unit Tests for Jackson and Gson JsonHandler
1 parent 734b4fe commit e1bba35

File tree

8 files changed

+406
-0
lines changed

8 files changed

+406
-0
lines changed

build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ configurations {
3030
testCompile.extendsFrom compileOnly
3131
}
3232

33+
configurations {
34+
testCompile.extendsFrom compileOnly
35+
}
36+
3337
dependencies {
3438
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
3539
implementation 'com.google.guava:guava:29.0-jre'
@@ -46,6 +50,12 @@ dependencies {
4650
testImplementation 'org.mockito:mockito-core:3.5.13'
4751
testImplementation 'org.hamcrest:hamcrest:2.2'
4852

53+
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
54+
compileOnly group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.11.3'
55+
compileOnly group: 'jakarta.json.bind', name: 'jakarta.json.bind-api', version: '1.0.2'
56+
testImplementation group: 'org.eclipse', name: 'yasson', version: '1.0.8'
57+
58+
4959
// Lombok
5060
compileOnly 'org.projectlombok:lombok:1.18.12'
5161
annotationProcessor 'org.projectlombok:lombok:1.18.12'
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.meilisearch.sdk.json;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.reflect.TypeToken;
5+
6+
public class GsonJsonHandler implements JsonHandler {
7+
private final Gson gson;
8+
9+
public GsonJsonHandler() {
10+
this.gson = new Gson();
11+
}
12+
13+
public GsonJsonHandler(Gson gson) {
14+
this.gson = gson;
15+
}
16+
17+
@Override
18+
public String encode(Object o) throws Exception {
19+
if (o.getClass() == String.class) {
20+
return (String) o;
21+
}
22+
try {
23+
return gson.toJson(o);
24+
} catch (Exception e) {
25+
// todo: use dedicated exception
26+
throw new RuntimeException("Error while serializing: ", e);
27+
}
28+
}
29+
30+
@Override
31+
@SuppressWarnings("unchecked")
32+
public <T> T decode(Object o, Class<?> targetClass, Class<?>... parameters) throws Exception {
33+
if (o == null) {
34+
// todo: use dedicated exception
35+
throw new RuntimeException("String to deserialize is null");
36+
}
37+
if (targetClass == String.class) {
38+
return (T) o;
39+
}
40+
try {
41+
if (parameters == null || parameters.length == 0) {
42+
return (T) gson.fromJson((String) o, targetClass);
43+
} else {
44+
TypeToken<?> parameterized = TypeToken.getParameterized(targetClass, parameters);
45+
return gson.fromJson((String) o, parameterized.getType());
46+
}
47+
} catch (Exception e) {
48+
// todo: use dedicated exception
49+
throw new RuntimeException("Error while deserializing: ", e);
50+
}
51+
}
52+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.meilisearch.sdk.json;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.DeserializationFeature;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
7+
import java.io.IOException;
8+
9+
public class JacksonJsonHandler implements JsonHandler {
10+
11+
private final ObjectMapper mapper;
12+
13+
/**
14+
* this constructor uses a default ObjectMapper with enabled 'FAIL_ON_UNKNOWN_PROPERTIES' feature.
15+
*/
16+
public JacksonJsonHandler() {
17+
this.mapper = new ObjectMapper();
18+
this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
19+
}
20+
21+
/**
22+
* @param mapper ObjectMapper
23+
*/
24+
public JacksonJsonHandler(ObjectMapper mapper) {
25+
this.mapper = mapper;
26+
}
27+
28+
/**
29+
* {@inheritDoc}
30+
*/
31+
@Override
32+
public String encode(Object o) throws Exception {
33+
if (o.getClass() == String.class) {
34+
return (String) o;
35+
}
36+
try {
37+
return mapper.writeValueAsString(o);
38+
} catch (JsonProcessingException e) {
39+
// todo: use dedicated exception
40+
throw new RuntimeException("Error while serializing: ", e);
41+
}
42+
}
43+
44+
/**
45+
* {@inheritDoc}
46+
*/
47+
@SuppressWarnings("unchecked")
48+
@Override
49+
public <T> T decode(Object o, Class<?> targetClass, Class<?>... parameters) throws Exception {
50+
if (o == null) {
51+
// todo: use dedicated exception
52+
throw new RuntimeException("String to deserialize is null");
53+
}
54+
if (targetClass == String.class) {
55+
return (T) o;
56+
}
57+
try {
58+
if (parameters == null || parameters.length == 0) {
59+
return (T) mapper.readValue((String) o, targetClass);
60+
} else {
61+
return mapper.readValue((String) o, mapper.getTypeFactory().constructParametricType(targetClass, parameters));
62+
}
63+
} catch (IOException e) {
64+
// todo: use dedicated exception
65+
throw new RuntimeException("Error while serializing: ", e);
66+
}
67+
}
68+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.meilisearch.sdk.json;
2+
3+
public interface JsonHandler {
4+
/**
5+
*
6+
* @param o the Object to serialize
7+
* @return the serialized Object {@code o}
8+
* @throws Exception wrapped exceptions of the used json library
9+
*/
10+
String encode(Object o) throws Exception;
11+
12+
/**
13+
* e.g. deserialize(somesthing, List.class, String.class) will try to deserialize "somestring" into a List<String>
14+
*
15+
* @param o Object to deserialize, most of the time this is a string
16+
* @param targetClass return type
17+
* @param parameters in case the return type is a generic class, this is a list of types to use with that generic.
18+
* @return the deserialized object
19+
* @throws Exception wrapped exceptions of the used json library
20+
*/
21+
<T> T decode(Object o, Class<?> targetClass, Class<?>... parameters) throws Exception;
22+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.meilisearch.sdk.json;
2+
3+
import javax.json.bind.Jsonb;
4+
import javax.json.bind.JsonbBuilder;
5+
import javax.json.bind.JsonbConfig;
6+
import javax.json.bind.JsonbException;
7+
8+
public class JsonbJsonHandler implements JsonHandler {
9+
10+
private final Jsonb mapper;
11+
12+
/**
13+
* this constructor uses a default ObjectMapper with enabled 'FAIL_ON_UNKNOWN_PROPERTIES' feature.
14+
*/
15+
public JsonbJsonHandler() {
16+
JsonbConfig config = new JsonbConfig().withNullValues(false);
17+
this.mapper = JsonbBuilder.create(config);
18+
}
19+
20+
/**
21+
* @param mapper ObjectMapper
22+
*/
23+
public JsonbJsonHandler(Jsonb mapper) {
24+
this.mapper = mapper;
25+
}
26+
27+
/**
28+
* {@inheritDoc}
29+
*/
30+
@Override
31+
public String encode(Object o) throws Exception {
32+
if (o.getClass() == String.class) {
33+
return (String) o;
34+
}
35+
try {
36+
return mapper.toJson(o);
37+
} catch (JsonbException e) {
38+
// todo: use dedicated exception
39+
throw new RuntimeException("Error while serializing: ", e);
40+
}
41+
}
42+
43+
/**
44+
* {@inheritDoc}
45+
*/
46+
@SuppressWarnings("unchecked")
47+
@Override
48+
public <T> T decode(Object o, Class<?> targetClass, Class<?>... parameters) throws Exception {
49+
if (o == null) {
50+
// todo: use dedicated exception
51+
throw new RuntimeException("String to deserialize is null");
52+
}
53+
if (targetClass == String.class) {
54+
return (T) o;
55+
}
56+
return (T) mapper.fromJson((String) o, targetClass);
57+
}
58+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.meilisearch.sdk.json;
2+
3+
import com.google.gson.Gson;
4+
import com.meilisearch.sdk.utils.Movie;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.util.HashMap;
8+
import java.util.List;
9+
10+
import static org.hamcrest.MatcherAssert.assertThat;
11+
import static org.hamcrest.Matchers.aMapWithSize;
12+
import static org.hamcrest.Matchers.notNullValue;
13+
import static org.junit.jupiter.api.Assertions.*;
14+
import static org.mockito.ArgumentMatchers.any;
15+
import static org.mockito.Mockito.spy;
16+
import static org.mockito.Mockito.when;
17+
18+
class GsonJsonHandlerTest {
19+
20+
private final Gson mapper = spy(new Gson());
21+
private final GsonJsonHandler classToTest = new GsonJsonHandler(mapper);
22+
23+
@Test
24+
void serialize() throws Exception {
25+
assertEquals("test", classToTest.encode("test"));
26+
when(mapper.toJson(any(Movie.class))).thenThrow(new RuntimeException("Oh boy!"));
27+
assertThrows(RuntimeException.class, () -> classToTest.encode(new Movie()));
28+
}
29+
30+
@Test
31+
void deserialize() {
32+
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class));
33+
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class, null));
34+
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class, new Class[0]));
35+
}
36+
37+
@Test
38+
void deserializeString() throws Exception {
39+
String content = "{}";
40+
assertEquals(content, classToTest.decode(content, String.class));
41+
}
42+
43+
@Test
44+
void deserializeBodyNull() {
45+
assertThrows(Exception.class, () -> classToTest.decode(null, List.class, String.class));
46+
}
47+
48+
@Test
49+
@SuppressWarnings({"RedundantArrayCreation", "ConfusingArgumentToVarargsMethod"})
50+
void deserializeWithParametersEmpty() throws Exception {
51+
assertNotNull(classToTest.decode("{}", Movie.class, null));
52+
assertNotNull(classToTest.decode("{}", Movie.class, new Class[0]));
53+
}
54+
55+
@Test
56+
void deserializeMap() throws Exception {
57+
String mapString = "{\"commitSha\":\"b46889b5f0f2f8b91438a08a358ba8f05fc09fc1\",\"buildDate\":\"2019-11-15T09:51:54.278247+00:00\",\"pkgVersion\":\"0.1.1\"}";
58+
HashMap<String, String> decode = classToTest.decode(mapString, HashMap.class, String.class, String.class);
59+
60+
assertThat(decode, notNullValue());
61+
assertThat(decode, aMapWithSize(3));
62+
}
63+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.meilisearch.sdk.json;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.meilisearch.sdk.utils.Movie;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.util.HashMap;
8+
import java.util.List;
9+
10+
import static org.hamcrest.MatcherAssert.assertThat;
11+
import static org.hamcrest.Matchers.aMapWithSize;
12+
import static org.hamcrest.Matchers.notNullValue;
13+
import static org.junit.jupiter.api.Assertions.*;
14+
import static org.mockito.ArgumentMatchers.any;
15+
import static org.mockito.Mockito.spy;
16+
import static org.mockito.Mockito.when;
17+
18+
class JacksonJsonHandlerTest {
19+
20+
private final ObjectMapper mapper = spy(new ObjectMapper());
21+
private final JacksonJsonHandler classToTest = new JacksonJsonHandler(mapper);
22+
23+
@Test
24+
void serialize() throws Exception {
25+
assertEquals("test", classToTest.encode("test"));
26+
when(mapper.writeValueAsString(any(Movie.class))).thenThrow(new RuntimeException("Oh boy!"));
27+
assertThrows(RuntimeException.class, () -> classToTest.encode(new Movie()));
28+
}
29+
30+
@Test
31+
void deserialize() {
32+
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class));
33+
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class, null));
34+
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class, new Class[0]));
35+
}
36+
37+
@Test
38+
void deserializeString() throws Exception {
39+
String content = "{}";
40+
assertEquals(content, classToTest.decode(content, String.class));
41+
}
42+
43+
@Test
44+
void deserializeBodyNull() {
45+
assertThrows(Exception.class, () -> classToTest.decode(null, List.class, String.class));
46+
}
47+
48+
@Test
49+
@SuppressWarnings({"RedundantArrayCreation", "ConfusingArgumentToVarargsMethod"})
50+
void deserializeWithParametersEmpty() throws Exception {
51+
assertNotNull(classToTest.decode("{}", Movie.class, null));
52+
assertNotNull(classToTest.decode("{}", Movie.class, new Class[0]));
53+
}
54+
55+
@Test
56+
void deserializeMap() throws Exception {
57+
String mapString = "{\"commitSha\":\"b46889b5f0f2f8b91438a08a358ba8f05fc09fc1\",\"buildDate\":\"2019-11-15T09:51:54.278247+00:00\",\"pkgVersion\":\"0.1.1\"}";
58+
HashMap<String, String> decode = classToTest.decode(mapString, HashMap.class, String.class, String.class);
59+
60+
assertThat(decode, notNullValue());
61+
assertThat(decode, aMapWithSize(3));
62+
}
63+
}

0 commit comments

Comments
 (0)