Skip to content

Commit

Permalink
feat: Add complex type support for JSON mappings.
Browse files Browse the repository at this point in the history
  • Loading branch information
nstdio committed Mar 27, 2022
1 parent 8ab69c3 commit 1617f77
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 108 deletions.
15 changes: 15 additions & 0 deletions src/main/java/io/github/nstdio/http/ext/spi/GsonJsonMapping.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.Objects;

import static java.nio.charset.StandardCharsets.UTF_8;
Expand All @@ -48,8 +49,22 @@ public <T> T read(InputStream in, Class<T> targetType) throws IOException {
}
}

@Override
public <T> T read(InputStream in, Type targetType) throws IOException {
try (var reader = new InputStreamReader(in, UTF_8)) {
return gson.fromJson(reader, targetType);
} catch (JsonParseException e) {
throw new IOException(e);
}
}

@Override
public <T> T read(byte[] bytes, Class<T> targetType) throws IOException {
return read(new ByteArrayInputStream(bytes), targetType);
}

@Override
public <T> T read(byte[] bytes, Type targetType) throws IOException {
return read(new ByteArrayInputStream(bytes), targetType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

package io.github.nstdio.http.ext.spi;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;

public class JacksonJsonMapping implements JsonMapping {
private final ObjectMapper mapper;
Expand All @@ -40,8 +42,25 @@ public <T> T read(InputStream in, Class<T> targetType) throws IOException {
}
}

@Override
public <T> T read(InputStream in, Type targetType) throws IOException {
try (var stream = in) {
return mapper.readValue(stream, constructType(targetType));
}
}

@Override
public <T> T read(byte[] bytes, Class<T> targetType) throws IOException {
return mapper.readValue(bytes, targetType);
}

@Override
public <T> T read(byte[] bytes, Type targetType) throws IOException {
return mapper.readValue(bytes, constructType(targetType));
}


private JavaType constructType(Type targetType) {
return mapper.constructType(targetType);
}
}
28 changes: 28 additions & 0 deletions src/main/java/io/github/nstdio/http/ext/spi/JsonMapping.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;

/**
* The simple strategy for binding JSON to Java objects.
Expand All @@ -37,6 +38,20 @@ public interface JsonMapping {
*/
<T> T read(InputStream in, Class<T> targetType) throws IOException;

/**
* Reads JSON data from the {@code in} and creates mapped object of type {@code targetType}. Note that {@code in}
* might not be closed by the underlying implementation and caller should try to close {@code in}.
*
* @param in The input source.
* @param targetType The required type.
* @param <T> The type of object to create.
*
* @return The object created from JSON.
*
* @throws IOException When there is a JSON parsing or binding error or I/O error occurred.
*/
<T> T read(InputStream in, Type targetType) throws IOException;

/**
* Reads JSON data from the {@code bytes} and creates mapped object of type {@code targetType}.
*
Expand All @@ -49,4 +64,17 @@ public interface JsonMapping {
* @throws IOException When there is a JSON parsing or binding error or I/O error occurred.
*/
<T> T read(byte[] bytes, Class<T> targetType) throws IOException;

/**
* Reads JSON data from the {@code bytes} and creates mapped object of type {@code targetType}.
*
* @param bytes The input source.
* @param targetType The required type.
* @param <T> The type of object to create.
*
* @return The object created from JSON.
*
* @throws IOException When there is a JSON parsing or binding error or I/O error occurred.
*/
<T> T read(byte[] bytes, Type targetType) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,57 +17,13 @@
package io.github.nstdio.http.ext.spi;

import io.github.nstdio.http.ext.jupiter.EnabledIfOnClasspath;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import static io.github.nstdio.http.ext.OptionalDependencies.GSON;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIOException;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

@EnabledIfOnClasspath(GSON)
class GsonJsonMappingTest {

@Test
void shouldThrowIOExceptionWhenParsingException() {
//given
var bytes = "{".getBytes(StandardCharsets.UTF_8);
var mapping = new GsonJsonMapping();

//when
assertThatIOException()
.isThrownBy(() -> mapping.read(bytes, Object.class))
.havingCause();
}

@Test
void shouldCloseInputStream() throws IOException {
//given
var inSpy = spy(new ByteArrayInputStream("{}".getBytes(StandardCharsets.UTF_8)));
var mapping = new GsonJsonMapping();

//when
Object read = mapping.read(inSpy, Object.class);

//then
assertThat(read).isNotNull();
verify(inSpy).close();
}

@Test
void shouldReadFromByteArray() throws IOException {
//given
var bytes = "{}".getBytes(StandardCharsets.UTF_8);
var mapping = new GsonJsonMapping();

//when
Object read = mapping.read(bytes, Object.class);

//then
assertThat(read).isNotNull();
public class GsonJsonMappingTest implements JsonMappingContract {
@Override
public JsonMapping get() {
return new GsonJsonMapping();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2022 Edgar Asatryan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.github.nstdio.http.ext.spi;

import io.github.nstdio.http.ext.jupiter.EnabledIfOnClasspath;

import static io.github.nstdio.http.ext.OptionalDependencies.JACKSON;

@EnabledIfOnClasspath(JACKSON)
public class JacksonJsonMappingTest implements JsonMappingContract {
@Override
public JsonMapping get() {
return new JacksonJsonMapping();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright (C) 2022 Edgar Asatryan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.github.nstdio.http.ext.spi;

import com.google.common.reflect.TypeToken;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;

interface JsonMappingContract {
JsonMapping get();

@Test
default void shouldThrowIOExceptionWhenParsingException() {
//given
var bytes = "{".getBytes(StandardCharsets.UTF_8);
var mapping = get();

//when
assertThrows(IOException.class, () -> mapping.read(bytes, Object.class));
}

@Test
default void shouldThrowIOExceptionWhenParsingExceptionWithComplexType() {
//given
var bytes = new ByteArrayInputStream("{".getBytes(StandardCharsets.UTF_8));
var mapping = get();
var targetType = new TypeToken<List<String>>() {}.getType();

//when
assertThrows(IOException.class, () -> mapping.read(bytes, targetType));
}

@Test
default void shouldCloseInputStream() throws IOException {
//given
byte[] jsonBytes = "{}".getBytes(StandardCharsets.UTF_8);
var inSpy = spy(new ByteArrayInputStream(jsonBytes));
var mapping = get();

//when
Object read = mapping.read(inSpy, Object.class);

//then
assertThat(read).isNotNull();
verify(inSpy, atLeastOnce()).close();
}

@Test
default void shouldReadFromByteArray() throws IOException {
//given
var jsonBytes = "{}".getBytes(StandardCharsets.UTF_8);
var mapping = get();

//when
Object read = mapping.read(jsonBytes, Object.class);

//then
assertThat(read).isNotNull();
}

@Test
default void shouldReadFromByteArrayUsingComplexType() throws IOException {
//given
var jsonBytes = "{\"a\": 1, \"b\": 2}".getBytes(StandardCharsets.UTF_8);
var mapping = get();
var targetType = new TypeToken<Map<String, Integer>>() {
}.getType();

//when
Map<String, Integer> read = mapping.read(jsonBytes, targetType);

//then
assertThat(read)
.hasSize(2)
.containsEntry("a", 1)
.containsEntry("b", 2);
}

@Test
default void shouldReadFromInputStreamUsingComplexType() throws IOException {
//given
var jsonBytes = new ByteArrayInputStream("{\"a\": 1, \"b\": 2}".getBytes(StandardCharsets.UTF_8));
var mapping = get();
var targetType = new TypeToken<Map<String, Integer>>() {
}.getType();

//when
Map<String, Integer> read = mapping.read(jsonBytes, targetType);

//then
assertThat(read)
.hasSize(2)
.containsEntry("a", 1)
.containsEntry("b", 2);
}
}

0 comments on commit 1617f77

Please sign in to comment.