Skip to content

centralize the serialization/deserialization logic #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ configurations {
testCompile.extendsFrom compileOnly
}

configurations {
testCompile.extendsFrom compileOnly
}

dependencies {
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:29.0-jre'
Expand All @@ -46,6 +50,12 @@ dependencies {
testImplementation 'org.mockito:mockito-core:3.5.13'
testImplementation 'org.hamcrest:hamcrest:2.2'

// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
compileOnly group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.11.3'
compileOnly group: 'jakarta.json.bind', name: 'jakarta.json.bind-api', version: '1.0.2'
testImplementation group: 'org.eclipse', name: 'yasson', version: '1.0.8'


// Lombok
compileOnly 'org.projectlombok:lombok:1.18.12'
annotationProcessor 'org.projectlombok:lombok:1.18.12'
Expand Down
52 changes: 52 additions & 0 deletions src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.meilisearch.sdk.json;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

public class GsonJsonHandler implements JsonHandler {
private final Gson gson;

public GsonJsonHandler() {
this.gson = new Gson();
}

public GsonJsonHandler(Gson gson) {
this.gson = gson;
}

@Override
public String encode(Object o) throws Exception {
if (o.getClass() == String.class) {
return (String) o;
}
try {
return gson.toJson(o);
} catch (Exception e) {
// todo: use dedicated exception
throw new RuntimeException("Error while serializing: ", e);
}
}

@Override
@SuppressWarnings("unchecked")
public <T> T decode(Object o, Class<?> targetClass, Class<?>... parameters) throws Exception {
if (o == null) {
// todo: use dedicated exception
throw new RuntimeException("String to deserialize is null");
}
if (targetClass == String.class) {
return (T) o;
}
try {
if (parameters == null || parameters.length == 0) {
return (T) gson.fromJson((String) o, targetClass);
} else {
TypeToken<?> parameterized = TypeToken.getParameterized(targetClass, parameters);
return gson.fromJson((String) o, parameterized.getType());
}
} catch (Exception e) {
// todo: use dedicated exception
throw new RuntimeException("Error while deserializing: ", e);
}
}
}
68 changes: 68 additions & 0 deletions src/main/java/com/meilisearch/sdk/json/JacksonJsonHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.meilisearch.sdk.json;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class JacksonJsonHandler implements JsonHandler {

private final ObjectMapper mapper;

/**
* this constructor uses a default ObjectMapper with enabled 'FAIL_ON_UNKNOWN_PROPERTIES' feature.
*/
public JacksonJsonHandler() {
this.mapper = new ObjectMapper();
this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}

/**
* @param mapper ObjectMapper
*/
public JacksonJsonHandler(ObjectMapper mapper) {
this.mapper = mapper;
}

/**
* {@inheritDoc}
*/
@Override
public String encode(Object o) throws Exception {
if (o.getClass() == String.class) {
return (String) o;
}
try {
return mapper.writeValueAsString(o);
} catch (JsonProcessingException e) {
// todo: use dedicated exception
throw new RuntimeException("Error while serializing: ", e);
}
}

/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <T> T decode(Object o, Class<?> targetClass, Class<?>... parameters) throws Exception {
if (o == null) {
// todo: use dedicated exception
throw new RuntimeException("String to deserialize is null");
}
if (targetClass == String.class) {
return (T) o;
}
try {
if (parameters == null || parameters.length == 0) {
return (T) mapper.readValue((String) o, targetClass);
} else {
return mapper.readValue((String) o, mapper.getTypeFactory().constructParametricType(targetClass, parameters));
}
} catch (IOException e) {
// todo: use dedicated exception
throw new RuntimeException("Error while serializing: ", e);
}
}
}
22 changes: 22 additions & 0 deletions src/main/java/com/meilisearch/sdk/json/JsonHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.meilisearch.sdk.json;

public interface JsonHandler {
/**
*
* @param o the Object to serialize
* @return the serialized Object {@code o}
* @throws Exception wrapped exceptions of the used json library
*/
String encode(Object o) throws Exception;

/**
* e.g. deserialize(somesthing, List.class, String.class) will try to deserialize "somestring" into a List<String>
*
* @param o Object to deserialize, most of the time this is a string
* @param targetClass return type
* @param parameters in case the return type is a generic class, this is a list of types to use with that generic.
* @return the deserialized object
* @throws Exception wrapped exceptions of the used json library
*/
<T> T decode(Object o, Class<?> targetClass, Class<?>... parameters) throws Exception;
}
58 changes: 58 additions & 0 deletions src/main/java/com/meilisearch/sdk/json/JsonbJsonHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.meilisearch.sdk.json;

import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.json.bind.JsonbException;

public class JsonbJsonHandler implements JsonHandler {

private final Jsonb mapper;

/**
* this constructor uses a default ObjectMapper with enabled 'FAIL_ON_UNKNOWN_PROPERTIES' feature.
*/
public JsonbJsonHandler() {
JsonbConfig config = new JsonbConfig().withNullValues(false);
this.mapper = JsonbBuilder.create(config);
}

/**
* @param mapper ObjectMapper
*/
public JsonbJsonHandler(Jsonb mapper) {
this.mapper = mapper;
}

/**
* {@inheritDoc}
*/
@Override
public String encode(Object o) throws Exception {
if (o.getClass() == String.class) {
return (String) o;
}
try {
return mapper.toJson(o);
} catch (JsonbException e) {
// todo: use dedicated exception
throw new RuntimeException("Error while serializing: ", e);
}
}

/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <T> T decode(Object o, Class<?> targetClass, Class<?>... parameters) throws Exception {
if (o == null) {
// todo: use dedicated exception
throw new RuntimeException("String to deserialize is null");
}
if (targetClass == String.class) {
return (T) o;
}
return (T) mapper.fromJson((String) o, targetClass);
}
}
63 changes: 63 additions & 0 deletions src/test/java/com/meilisearch/sdk/json/GsonJsonHandlerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.meilisearch.sdk.json;

import com.google.gson.Gson;
import com.meilisearch.sdk.utils.Movie;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.List;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

class GsonJsonHandlerTest {

private final Gson mapper = spy(new Gson());
private final GsonJsonHandler classToTest = new GsonJsonHandler(mapper);

@Test
void serialize() throws Exception {
assertEquals("test", classToTest.encode("test"));
when(mapper.toJson(any(Movie.class))).thenThrow(new RuntimeException("Oh boy!"));
assertThrows(RuntimeException.class, () -> classToTest.encode(new Movie()));
}

@Test
void deserialize() {
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class));
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class, null));
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class, new Class[0]));
}

@Test
void deserializeString() throws Exception {
String content = "{}";
assertEquals(content, classToTest.decode(content, String.class));
}

@Test
void deserializeBodyNull() {
assertThrows(Exception.class, () -> classToTest.decode(null, List.class, String.class));
}

@Test
@SuppressWarnings({"RedundantArrayCreation", "ConfusingArgumentToVarargsMethod"})
void deserializeWithParametersEmpty() throws Exception {
assertNotNull(classToTest.decode("{}", Movie.class, null));
assertNotNull(classToTest.decode("{}", Movie.class, new Class[0]));
}

@Test
void deserializeMap() throws Exception {
String mapString = "{\"commitSha\":\"b46889b5f0f2f8b91438a08a358ba8f05fc09fc1\",\"buildDate\":\"2019-11-15T09:51:54.278247+00:00\",\"pkgVersion\":\"0.1.1\"}";
HashMap<String, String> decode = classToTest.decode(mapString, HashMap.class, String.class, String.class);

assertThat(decode, notNullValue());
assertThat(decode, aMapWithSize(3));
}
}
63 changes: 63 additions & 0 deletions src/test/java/com/meilisearch/sdk/json/JacksonJsonHandlerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.meilisearch.sdk.json;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.meilisearch.sdk.utils.Movie;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.List;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

class JacksonJsonHandlerTest {

private final ObjectMapper mapper = spy(new ObjectMapper());
private final JacksonJsonHandler classToTest = new JacksonJsonHandler(mapper);

@Test
void serialize() throws Exception {
assertEquals("test", classToTest.encode("test"));
when(mapper.writeValueAsString(any(Movie.class))).thenThrow(new RuntimeException("Oh boy!"));
assertThrows(RuntimeException.class, () -> classToTest.encode(new Movie()));
}

@Test
void deserialize() {
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class));
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class, null));
assertDoesNotThrow(() -> classToTest.decode("{}", Movie.class, new Class[0]));
}

@Test
void deserializeString() throws Exception {
String content = "{}";
assertEquals(content, classToTest.decode(content, String.class));
}

@Test
void deserializeBodyNull() {
assertThrows(Exception.class, () -> classToTest.decode(null, List.class, String.class));
}

@Test
@SuppressWarnings({"RedundantArrayCreation", "ConfusingArgumentToVarargsMethod"})
void deserializeWithParametersEmpty() throws Exception {
assertNotNull(classToTest.decode("{}", Movie.class, null));
assertNotNull(classToTest.decode("{}", Movie.class, new Class[0]));
}

@Test
void deserializeMap() throws Exception {
String mapString = "{\"commitSha\":\"b46889b5f0f2f8b91438a08a358ba8f05fc09fc1\",\"buildDate\":\"2019-11-15T09:51:54.278247+00:00\",\"pkgVersion\":\"0.1.1\"}";
HashMap<String, String> decode = classToTest.decode(mapString, HashMap.class, String.class, String.class);

assertThat(decode, notNullValue());
assertThat(decode, aMapWithSize(3));
}
}
Loading