From 4506fb67ca504e016aca15385c12c22953dbac62 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 10 Sep 2020 09:21:15 +0200 Subject: [PATCH] #453 - Introduce converters to consume Postgres Json and convert these to String/byte[]. We now ship converters for Postgres' Json data type to map JSON to String and to byte[] for easier and safe consumption. --- .../data/r2dbc/dialect/PostgresDialect.java | 57 ++++++++++++++++--- ...ostgresMappingR2dbcConverterUnitTests.java | 37 ++++++++++++ 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java b/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java index c6e0d0c9..6fc78d42 100644 --- a/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java +++ b/src/main/java/org/springframework/data/r2dbc/dialect/PostgresDialect.java @@ -1,5 +1,7 @@ package org.springframework.data.r2dbc.dialect; +import io.r2dbc.postgresql.codec.Json; + import java.net.InetAddress; import java.net.URI; import java.net.URL; @@ -38,6 +40,9 @@ public class PostgresDialect extends org.springframework.data.relational.core.di private static final Set> SIMPLE_TYPES; + private static final boolean JSON_PRESENT = ClassUtils.isPresent("io.r2dbc.postgresql.codec.Json", + PostgresDialect.class.getClassLoader()); + private static final boolean GEO_TYPES_PRESENT = ClassUtils.isPresent("io.r2dbc.postgresql.codec.Polygon", PostgresDialect.class.getClassLoader()); @@ -45,9 +50,6 @@ public class PostgresDialect extends org.springframework.data.relational.core.di Set> simpleTypes = new HashSet<>(Arrays.asList(UUID.class, URL.class, URI.class, InetAddress.class)); - // conditional Postgres JSON support. - ifClassPresent("io.r2dbc.postgresql.codec.Json", simpleTypes::add); - // conditional Postgres Geo support. Stream.of("io.r2dbc.postgresql.codec.Box", // "io.r2dbc.postgresql.codec.Circle", // @@ -58,6 +60,9 @@ public class PostgresDialect extends org.springframework.data.relational.core.di "io.r2dbc.postgresql.codec.Polygon") // .forEach(s -> ifClassPresent(s, simpleTypes::add)); + // conditional Postgres JSON support. + ifClassPresent("io.r2dbc.postgresql.codec.Json", simpleTypes::add); + SIMPLE_TYPES = simpleTypes; } @@ -106,14 +111,24 @@ public class PostgresDialect extends org.springframework.data.relational.core.di @Override public Collection getConverters() { - if (GEO_TYPES_PRESENT) { - return Arrays.asList(FromPostgresPointConverter.INSTANCE, ToPostgresPointConverter.INSTANCE, // - FromPostgresCircleConverter.INSTANCE, ToPostgresCircleConverter.INSTANCE, // - FromPostgresBoxConverter.INSTANCE, ToPostgresBoxConverter.INSTANCE, // - FromPostgresPolygonConverter.INSTANCE, ToPostgresPolygonConverter.INSTANCE); + if (!GEO_TYPES_PRESENT && !JSON_PRESENT) { + return Collections.emptyList(); } - return Collections.emptyList(); + List converters = new ArrayList<>(); + + if (GEO_TYPES_PRESENT) { + converters.addAll(Arrays.asList(FromPostgresPointConverter.INSTANCE, ToPostgresPointConverter.INSTANCE, // + FromPostgresCircleConverter.INSTANCE, ToPostgresCircleConverter.INSTANCE, // + FromPostgresBoxConverter.INSTANCE, ToPostgresBoxConverter.INSTANCE, // + FromPostgresPolygonConverter.INSTANCE, ToPostgresPolygonConverter.INSTANCE)); + } + + if (JSON_PRESENT) { + converters.addAll(Arrays.asList(JsonToByteArrayConverter.INSTANCE, JsonToStringConverter.INSTANCE)); + } + + return converters; } private static class R2dbcArrayColumns implements ArrayColumns { @@ -269,4 +284,28 @@ public class PostgresDialect extends org.springframework.data.relational.core.di } } + @ReadingConverter + private enum JsonToStringConverter implements Converter { + + INSTANCE; + + @Override + @NonNull + public String convert(Json source) { + return source.asString(); + } + } + + @ReadingConverter + private enum JsonToByteArrayConverter implements Converter { + + INSTANCE; + + @Override + @NonNull + public byte[] convert(Json source) { + return source.asArray(); + } + } + } diff --git a/src/test/java/org/springframework/data/r2dbc/convert/PostgresMappingR2dbcConverterUnitTests.java b/src/test/java/org/springframework/data/r2dbc/convert/PostgresMappingR2dbcConverterUnitTests.java index 4860cd1d..43665daa 100644 --- a/src/test/java/org/springframework/data/r2dbc/convert/PostgresMappingR2dbcConverterUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/convert/PostgresMappingR2dbcConverterUnitTests.java @@ -18,6 +18,9 @@ package org.springframework.data.r2dbc.convert; import static org.assertj.core.api.Assertions.*; import io.r2dbc.postgresql.codec.Json; +import io.r2dbc.spi.test.MockColumnMetadata; +import io.r2dbc.spi.test.MockRow; +import io.r2dbc.spi.test.MockRowMetadata; import lombok.AllArgsConstructor; import java.util.ArrayList; @@ -72,6 +75,30 @@ public class PostgresMappingR2dbcConverterUnitTests { assertThat(row).containsEntry(SqlIdentifier.unquoted("json_value"), Parameter.from(person.jsonValue)); } + @Test // gh-453 + public void shouldConvertJsonToString() { + + MockRow row = MockRow.builder().identified("json_string", Object.class, Json.of("{\"hello\":\"world\"}")).build(); + + MockRowMetadata metadata = MockRowMetadata.builder() + .columnMetadata(MockColumnMetadata.builder().name("json_string").build()).build(); + + ConvertedJson result = converter.read(ConvertedJson.class, row, metadata); + assertThat(result.jsonString).isEqualTo("{\"hello\":\"world\"}"); + } + + @Test // gh-453 + public void shouldConvertJsonToByteArray() { + + MockRow row = MockRow.builder().identified("json_bytes", Object.class, Json.of("{\"hello\":\"world\"}")).build(); + + MockRowMetadata metadata = MockRowMetadata.builder() + .columnMetadata(MockColumnMetadata.builder().name("json_bytes").build()).build(); + + ConvertedJson result = converter.read(ConvertedJson.class, row, metadata); + assertThat(result.jsonBytes).isEqualTo("{\"hello\":\"world\"}".getBytes()); + } + @AllArgsConstructor static class JsonPerson { @@ -79,4 +106,14 @@ public class PostgresMappingR2dbcConverterUnitTests { Json jsonValue; } + + @AllArgsConstructor + static class ConvertedJson { + + @Id Long id; + + String jsonString; + + byte[] jsonBytes; + } }