Skip to content
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

Unwrap maps when serializing #13

Merged
merged 3 commits into from
Jun 8, 2018
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
9 changes: 9 additions & 0 deletions algebra-jackson/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,16 @@
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-guava</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
import static com.hubspot.algebra.ResultModule.OK_FIELD_NAME;

import java.io.IOException;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;
import com.hubspot.algebra.ResultModule.Case;

public class ResultSerializer extends StdSerializer<Result<?, ?>> {
Expand Down Expand Up @@ -41,11 +45,37 @@ private static void serializeValue(
JsonGenerator gen,
SerializerProvider provider
) throws IOException {
JsonSerializer<Object> serializer = provider.findTypedValueSerializer(value.getClass(), true, null)
Object flattenedValue = flattenValue(value);
JsonSerializer<Object> serializer = provider.findTypedValueSerializer(flattenedValue.getClass(), true, null)
.unwrappingSerializer(null);
if (!serializer.isUnwrappingSerializer()) {
gen.writeFieldName(fieldName);
}
serializer.serialize(value, gen, provider);
serializer.serialize(flattenedValue, gen, provider);
}

private static Object flattenValue(Object value) {
if (value instanceof Map) {
return new MapFlattener((Map<?, ?>) value);
} else if (value instanceof Multimap) {
return new MapFlattener(((Multimap<?, ?>) value).asMap());
} else if (value instanceof Table) {
return new MapFlattener(((Table<?, ?, ?>) value).rowMap());
} else {
return value;
}
}

private static class MapFlattener {
private final Map<?, ?> map;

private MapFlattener(Map<?, ?> map) {
this.map = map;
}

@JsonAnyGetter
public Map<?, ?> getMap() {
return map;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.hubspot.algebra;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.io.IOException;
import java.util.Arrays;
Expand All @@ -18,37 +19,61 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Multimap;
import com.google.common.collect.Table;

public class ResultModuleTest {

private static final Result<TestBean, TestError> BEAN_OK = Result.ok(new TestBean("test"));
private static final String BEAN_OK_JSON = "{\"value\":\"test\",\"@result\":\"OK\"}";
private static final Result<TestBean, TestBean> BEAN_ERR = Result.err(new TestBean("ERROR"));
private static final String BEAN_ERR_JSON = "{\"value\":\"ERROR\",\"@result\":\"ERR\"}";

private static final Result<TestBean, TestError> CUSTOM_ENUM_ERR = Result.err(TestError.ERROR);
private static final String CUSTOM_ENUM_ERR_JSON = "{\"name\":\"ERROR\",\"@result\":\"ERR\"}";
private static final Result<TestBean, RawError> RAW_ENUM_ERR = Result.err(RawError.ERROR);
private static final String RAW_ENUM_ERR_JSON = "{\"@error\":\"ERROR\",\"@result\":\"ERR\"}";

private static final Result<String, String> STRING_OK = Result.ok("test");
private static final String STRING_OK_JSON = "{\"@ok\":\"test\",\"@result\":\"OK\"}";
private static final Result<String, String> STRING_ERR = Result.err("ERROR");
private static final String STRING_ERR_JSON = "{\"@error\":\"ERROR\",\"@result\":\"ERR\"}";

private static final Result<List<String>, List<String>> LIST_OK = Result.ok(Arrays.asList("val0", "val1"));
private static final String LIST_OK_JSON = "{\"@ok\":[\"val0\",\"val1\"],\"@result\":\"OK\"}";
private static final Result<List<String>, List<String>> LIST_ERR = Result.err(Arrays.asList("err0", "err1"));
private static final String LIST_ERR_JSON = "{\"@error\":[\"err0\",\"err1\"],\"@result\":\"ERR\"}";

private static final Result<Map<String, String>, Map<String, String>> MAP_OK = Result.ok(Collections.singletonMap("key", "value"));
private static final String MAP_OK_JSON = "{\"@ok\":{\"key\":\"value\"},\"@result\":\"OK\"}";
private static final String MAP_OK_JSON = "{\"key\":\"value\",\"@result\":\"OK\"}";
private static final Result<Map<String, String>, Map<String, String>> MAP_ERR = Result.err(Collections.singletonMap("key", "value"));
private static final String MAP_ERR_JSON = "{\"@error\":{\"key\":\"value\"},\"@result\":\"ERR\"}";
private static final String MAP_ERR_JSON = "{\"key\":\"value\",\"@result\":\"ERR\"}";

private static final Result<Multimap<String, String>, Multimap<String, String>> MULTIMAP_OK = Result.ok(
ImmutableMultimap.<String, String> builder().putAll("key", "val0", "val1").build());
private static final String MULTIMAP_OK_JSON = "{\"key\":[\"val0\",\"val1\"],\"@result\":\"OK\"}";
private static final Result<Multimap<String, String>, Multimap<String, String>> MULTIMAP_ERR = Result.err(
ImmutableMultimap.<String, String> builder().putAll("key", "err0", "err1").build());
private static final String MULTIMAP_ERR_JSON = "{\"key\":[\"err0\",\"err1\"],\"@result\":\"ERR\"}";

private static final Result<Table<String, String, String>, Table<String, String, String>> TABLE_OK = Result.ok(
ImmutableTable.<String, String, String> builder().put("row", "column", "value").build());
private static final String TABLE_OK_JSON = "{\"row\":{\"column\":\"value\"},\"@result\":\"OK\"}";
private static final Result<Table<String, String, String>, Table<String, String, String>> TABLE_ERR = Result.err(
ImmutableTable.<String, String, String> builder().put("row", "column", "value").build());
private static final String TABLE_ERR_JSON = "{\"row\":{\"column\":\"value\"},\"@result\":\"ERR\"}";

private static ObjectMapper objectMapper;

@BeforeClass
public static void setupClass() {
objectMapper = new ObjectMapper().registerModule(new ResultModule());
objectMapper = new ObjectMapper().registerModules(new ResultModule(), new GuavaModule());
}

@Test
Expand Down Expand Up @@ -101,6 +126,26 @@ public void itSerializesMapErr() throws Exception {
itSerializes(MAP_ERR, MAP_ERR_JSON);
}

@Test
public void itSerializesMultimapOk() throws Exception {
itSerializes(MULTIMAP_OK, MULTIMAP_OK_JSON);
}

@Test
public void itSerializesMultimapErr() throws Exception {
itSerializes(MULTIMAP_ERR, MULTIMAP_ERR_JSON);
}

@Test
public void itSerializesTableOk() throws Exception {
itSerializes(TABLE_OK, TABLE_OK_JSON);
}

@Test
public void itSerializesTableErr() throws Exception {
itSerializes(TABLE_ERR, TABLE_ERR_JSON);
}

@Test
public void itDeserializesBeanOk() throws Exception {
itDeserializes(
Expand Down Expand Up @@ -191,6 +236,45 @@ public void itDeserializesMapErr() throws Exception {
);
}

@Test
public void itDeserializesMultimapOk() throws Exception {
itDeserializes(
MULTIMAP_OK_JSON,
new TypeReference<Result<Multimap<String, String>, Multimap<String, String>>>() {
},
MULTIMAP_OK
);
}

@Test
public void itDeserializesMultimapErr() throws Exception {
itDeserializes(
MULTIMAP_ERR_JSON,
new TypeReference<Result<Multimap<String, String>, Multimap<String, String>>>(){},
MULTIMAP_ERR
);
}

@Test
public void itDeserializesTableOk() throws Exception {
assertThatThrownBy(() -> itDeserializes(
TABLE_OK_JSON,
new TypeReference<Result<Table<String, String, String>, Table<String, String, String>>>(){},
TABLE_OK
)).isInstanceOf(JsonMappingException.class)
.hasMessageStartingWith("Can not construct instance of com.google.common.collect.Table");
}

@Test
public void itDeserializesTableErr() throws Exception {
assertThatThrownBy(() -> itDeserializes(
TABLE_ERR_JSON,
new TypeReference<Result<Table<String, String, String>, Table<String, String, String>>>(){},
TABLE_ERR
)).isInstanceOf(JsonMappingException.class)
.hasMessageStartingWith("Can not construct instance of com.google.common.collect.Table");
}

private void itSerializes(Result<?, ?> result, String expectedJson) throws JsonProcessingException {
assertThat(objectMapper.writeValueAsString(result)).isEqualTo(expectedJson);
}
Expand Down