From aed4546c437bc0990f3282fa9a943fad244d1588 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 9 Oct 2024 19:06:00 -0700 Subject: [PATCH] Extract `WritableJson` from `JsonWriter` Make `WritableJson` a top level class rather than a nested class inside `WritableJson`. Closes gh-42595 --- .../boot/json/JsonValueWriter.java | 1 - .../springframework/boot/json/JsonWriter.java | 150 --------------- .../boot/json/WritableJson.java | 175 ++++++++++++++++++ ...tendedLogFormatStructuredLogFormatter.java | 2 +- .../logging/log4j2/StructuredMessage.java | 2 +- ...tendedLogFormatStructuredLogFormatter.java | 2 +- .../boot/json/JsonWriterTests.java | 127 ------------- .../boot/json/WritableJsonTests.java | 151 +++++++++++++++ 8 files changed, 329 insertions(+), 281 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/WritableJson.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/json/WritableJsonTests.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/JsonValueWriter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/JsonValueWriter.java index 00194b5504..6c7017b253 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/JsonValueWriter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/JsonValueWriter.java @@ -25,7 +25,6 @@ import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; -import org.springframework.boot.json.JsonWriter.WritableJson; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.function.ThrowingConsumer; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/JsonWriter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/JsonWriter.java index 6d11c4ef8c..03ae21ce46 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/JsonWriter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/JsonWriter.java @@ -16,14 +16,7 @@ package org.springframework.boot.json; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UncheckedIOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -37,7 +30,6 @@ import java.util.function.Supplier; import org.springframework.boot.json.JsonValueWriter.Series; import org.springframework.boot.json.JsonWriter.Member.Extractor; -import org.springframework.core.io.WritableResource; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -159,148 +151,6 @@ public interface JsonWriter { return (instance, out) -> initializedMembers.write(instance, new JsonValueWriter(out)); } - /** - * JSON content that can be written out. - */ - @FunctionalInterface - interface WritableJson { - - /** - * Write the JSON to the provided {@link Appendable}. - * @param out the {@link Appendable} to receive the JSON - * @throws IOException on IO error - */ - void to(Appendable out) throws IOException; - - /** - * Write the JSON to a {@link String}. - * @return the JSON string - */ - default String toJsonString() { - try { - StringBuilder stringBuilder = new StringBuilder(); - to(stringBuilder); - return stringBuilder.toString(); - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } - } - - /** - * Write the JSON to a UTF-8 encoded byte array. - * @return the JSON bytes - */ - default byte[] toByteArray() { - return toByteArray(StandardCharsets.UTF_8); - } - - /** - * Write the JSON to a byte array. - * @param charset the charset - * @return the JSON bytes - */ - default byte[] toByteArray(Charset charset) { - Assert.notNull(charset, "'charset' must not be null"); - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - toWriter(new OutputStreamWriter(out, charset)); - return out.toByteArray(); - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } - } - - /** - * Write the JSON to the provided {@link WritableResource} using - * {@link StandardCharsets#UTF_8 UTF8} encoding. - * @param out the {@link OutputStream} to receive the JSON - * @throws IOException on IO error - */ - default void toResource(WritableResource out) throws IOException { - Assert.notNull(out, "'out' must not be null"); - try (OutputStream outputStream = out.getOutputStream()) { - toOutputStream(outputStream); - } - } - - /** - * Write the JSON to the provided {@link WritableResource} using the given - * {@link Charset}. - * @param out the {@link OutputStream} to receive the JSON - * @param charset the charset to use - * @throws IOException on IO error - */ - default void toResource(WritableResource out, Charset charset) throws IOException { - Assert.notNull(out, "'out' must not be null"); - Assert.notNull(charset, "'charset' must not be null"); - try (OutputStream outputStream = out.getOutputStream()) { - toOutputStream(outputStream, charset); - } - } - - /** - * Write the JSON to the provided {@link OutputStream} using - * {@link StandardCharsets#UTF_8 UTF8} encoding. The output stream will not be - * closed. - * @param out the {@link OutputStream} to receive the JSON - * @throws IOException on IO error - * @see #toOutputStream(OutputStream, Charset) - */ - default void toOutputStream(OutputStream out) throws IOException { - toOutputStream(out, StandardCharsets.UTF_8); - } - - /** - * Write the JSON to the provided {@link OutputStream} using the given - * {@link Charset}. The output stream will not be closed. - * @param out the {@link OutputStream} to receive the JSON - * @param charset the charset to use - * @throws IOException on IO error - */ - default void toOutputStream(OutputStream out, Charset charset) throws IOException { - Assert.notNull(out, "'out' must not be null"); - Assert.notNull(charset, "'charset' must not be null"); - toWriter(new OutputStreamWriter(out, charset)); - } - - /** - * Write the JSON to the provided {@link Writer}. The writer will be flushed but - * not closed. - * @param out the {@link Writer} to receive the JSON - * @throws IOException on IO error - * @see #toOutputStream(OutputStream, Charset) - */ - default void toWriter(Writer out) throws IOException { - Assert.notNull(out, "'out' must not be null"); - to(out); - out.flush(); - } - - /** - * Factory method used to create a {@link WritableJson} with a sensible - * {@link Object#toString()} that delegate to {@link WritableJson#toJsonString()}. - * @param writableJson the source {@link WritableJson} - * @return a new {@link WritableJson} with a sensible {@link Object#toString()}. - */ - static WritableJson of(WritableJson writableJson) { - return new WritableJson() { - - @Override - public void to(Appendable out) throws IOException { - writableJson.to(out); - } - - @Override - public String toString() { - return toJsonString(); - } - - }; - } - - } - /** * Callback used to configure JSON members. Individual members can be declared using * the various {@code add(...)} methods. Typically, members are declared with a diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/WritableJson.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/WritableJson.java new file mode 100644 index 0000000000..49d49a483f --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/WritableJson.java @@ -0,0 +1,175 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * 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 + * + * https://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 org.springframework.boot.json; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.springframework.core.io.WritableResource; +import org.springframework.util.Assert; + +/** + * JSON content that can be written out. + * + * @author Phillip Webb + * @author Moritz Halbritter + * @since 3.4.0 + * @see JsonWriter + */ +@FunctionalInterface +public interface WritableJson { + + /** + * Write the JSON to the provided {@link Appendable}. + * @param out the {@link Appendable} to receive the JSON + * @throws IOException on IO error + */ + void to(Appendable out) throws IOException; + + /** + * Write the JSON to a {@link String}. + * @return the JSON string + */ + default String toJsonString() { + try { + StringBuilder stringBuilder = new StringBuilder(); + to(stringBuilder); + return stringBuilder.toString(); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + /** + * Write the JSON to a UTF-8 encoded byte array. + * @return the JSON bytes + */ + default byte[] toByteArray() { + return toByteArray(StandardCharsets.UTF_8); + } + + /** + * Write the JSON to a byte array. + * @param charset the charset + * @return the JSON bytes + */ + default byte[] toByteArray(Charset charset) { + Assert.notNull(charset, "'charset' must not be null"); + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + toWriter(new OutputStreamWriter(out, charset)); + return out.toByteArray(); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + /** + * Write the JSON to the provided {@link WritableResource} using + * {@link StandardCharsets#UTF_8 UTF8} encoding. + * @param out the {@link OutputStream} to receive the JSON + * @throws IOException on IO error + */ + default void toResource(WritableResource out) throws IOException { + Assert.notNull(out, "'out' must not be null"); + try (OutputStream outputStream = out.getOutputStream()) { + toOutputStream(outputStream); + } + } + + /** + * Write the JSON to the provided {@link WritableResource} using the given + * {@link Charset}. + * @param out the {@link OutputStream} to receive the JSON + * @param charset the charset to use + * @throws IOException on IO error + */ + default void toResource(WritableResource out, Charset charset) throws IOException { + Assert.notNull(out, "'out' must not be null"); + Assert.notNull(charset, "'charset' must not be null"); + try (OutputStream outputStream = out.getOutputStream()) { + toOutputStream(outputStream, charset); + } + } + + /** + * Write the JSON to the provided {@link OutputStream} using + * {@link StandardCharsets#UTF_8 UTF8} encoding. The output stream will not be closed. + * @param out the {@link OutputStream} to receive the JSON + * @throws IOException on IO error + * @see #toOutputStream(OutputStream, Charset) + */ + default void toOutputStream(OutputStream out) throws IOException { + toOutputStream(out, StandardCharsets.UTF_8); + } + + /** + * Write the JSON to the provided {@link OutputStream} using the given + * {@link Charset}. The output stream will not be closed. + * @param out the {@link OutputStream} to receive the JSON + * @param charset the charset to use + * @throws IOException on IO error + */ + default void toOutputStream(OutputStream out, Charset charset) throws IOException { + Assert.notNull(out, "'out' must not be null"); + Assert.notNull(charset, "'charset' must not be null"); + toWriter(new OutputStreamWriter(out, charset)); + } + + /** + * Write the JSON to the provided {@link Writer}. The writer will be flushed but not + * closed. + * @param out the {@link Writer} to receive the JSON + * @throws IOException on IO error + * @see #toOutputStream(OutputStream, Charset) + */ + default void toWriter(Writer out) throws IOException { + Assert.notNull(out, "'out' must not be null"); + to(out); + out.flush(); + } + + /** + * Factory method used to create a {@link WritableJson} with a sensible + * {@link Object#toString()} that delegate to {@link WritableJson#toJsonString()}. + * @param writableJson the source {@link WritableJson} + * @return a new {@link WritableJson} with a sensible {@link Object#toString()}. + */ + static WritableJson of(WritableJson writableJson) { + return new WritableJson() { + + @Override + public void to(Appendable out) throws IOException { + writableJson.to(out); + } + + @Override + public String toString() { + return toJsonString(); + } + + }; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/GraylogExtendedLogFormatStructuredLogFormatter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/GraylogExtendedLogFormatStructuredLogFormatter.java index 5138b50353..8afb7b7c3a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/GraylogExtendedLogFormatStructuredLogFormatter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/GraylogExtendedLogFormatStructuredLogFormatter.java @@ -33,7 +33,7 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.springframework.boot.json.JsonWriter; import org.springframework.boot.json.JsonWriter.Members; -import org.springframework.boot.json.JsonWriter.WritableJson; +import org.springframework.boot.json.WritableJson; import org.springframework.boot.logging.structured.CommonStructuredLogFormat; import org.springframework.boot.logging.structured.GraylogExtendedLogFormatService; import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredMessage.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredMessage.java index 873ee2814c..3e0f08aa9b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredMessage.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredMessage.java @@ -21,7 +21,7 @@ import java.io.IOException; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.util.MultiFormatStringBuilderFormattable; -import org.springframework.boot.json.JsonWriter.WritableJson; +import org.springframework.boot.json.WritableJson; /** * Helper used to adapt {@link Message} for structured writing. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/GraylogExtendedLogFormatStructuredLogFormatter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/GraylogExtendedLogFormatStructuredLogFormatter.java index b2ddd304d1..760b5ca319 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/GraylogExtendedLogFormatStructuredLogFormatter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/GraylogExtendedLogFormatStructuredLogFormatter.java @@ -33,7 +33,7 @@ import org.slf4j.event.KeyValuePair; import org.springframework.boot.json.JsonWriter; import org.springframework.boot.json.JsonWriter.Members; -import org.springframework.boot.json.JsonWriter.WritableJson; +import org.springframework.boot.json.WritableJson; import org.springframework.boot.logging.structured.CommonStructuredLogFormat; import org.springframework.boot.logging.structured.GraylogExtendedLogFormatService; import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/json/JsonWriterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/json/JsonWriterTests.java index 290f427cd7..2f803a8aac 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/json/JsonWriterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/json/JsonWriterTests.java @@ -16,12 +16,6 @@ package org.springframework.boot.json; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -31,15 +25,10 @@ import java.util.function.Function; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.json.JsonWriter.PairExtractor; -import org.springframework.boot.json.JsonWriter.WritableJson; -import org.springframework.core.io.FileSystemResource; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** @@ -52,9 +41,6 @@ class JsonWriterTests { private static final Person PERSON = new Person("Spring", "Boot", 10); - @TempDir - File temp; - @Test void writeToStringWritesToString() { assertThat(ofFormatString("%s").writeToString(123)).isEqualTo("123"); @@ -468,119 +454,6 @@ class JsonWriterTests { } - @Nested - class WritableJsonTests { - - @Test - void toJsonStringReturnsString() { - WritableJson writable = (out) -> out.append("{}"); - assertThat(writable.toJsonString()).isEqualTo("{}"); - } - - @Test - void toJsonStringWhenIOExceptionIsThrownThrowsUncheckedIOException() { - WritableJson writable = (out) -> { - throw new IOException("bad"); - }; - assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(() -> writable.toJsonString()) - .havingCause() - .withMessage("bad"); - } - - @Test - void toByteArrayReturnsByteArray() { - WritableJson writable = (out) -> out.append("{}"); - assertThat(writable.toByteArray()).isEqualTo("{}".getBytes()); - } - - @Test - void toResourceWritesJson() throws Exception { - File file = new File(JsonWriterTests.this.temp, "out.json"); - WritableJson writable = (out) -> out.append("{}"); - writable.toResource(new FileSystemResource(file)); - assertThat(file).content().isEqualTo("{}"); - } - - @Test - void toResourceWithCharsetWritesJson() throws Exception { - File file = new File(JsonWriterTests.this.temp, "out.json"); - WritableJson writable = (out) -> out.append("{}"); - writable.toResource(new FileSystemResource(file), StandardCharsets.ISO_8859_1); - assertThat(file).content(StandardCharsets.ISO_8859_1).isEqualTo("{}"); - } - - @Test - void toResourceWithCharsetWhenOutIsNullThrowsException() { - WritableJson writable = (out) -> out.append("{}"); - assertThatIllegalArgumentException().isThrownBy(() -> writable.toResource(null, StandardCharsets.UTF_8)) - .withMessage("'out' must not be null"); - } - - @Test - void toResourceWithCharsetWhenCharsetIsNullThrowsException() { - File file = new File(JsonWriterTests.this.temp, "out.json"); - WritableJson writable = (out) -> out.append("{}"); - assertThatIllegalArgumentException() - .isThrownBy(() -> writable.toResource(new FileSystemResource(file), null)) - .withMessage("'charset' must not be null"); - } - - @Test - void toOutputStreamWritesJson() throws Exception { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - WritableJson writable = (out) -> out.append("{}"); - writable.toOutputStream(outputStream); - assertThat(outputStream.toString(StandardCharsets.UTF_8)).isEqualTo("{}"); - } - - @Test - void toOutputStreamWithCharsetWritesJson() throws Exception { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - WritableJson writable = (out) -> out.append("{}"); - writable.toOutputStream(outputStream, StandardCharsets.ISO_8859_1); - assertThat(outputStream.toString(StandardCharsets.ISO_8859_1)).isEqualTo("{}"); - } - - @Test - void toOutputStreamWithCharsetWhenOutIsNullThrowsException() { - WritableJson writable = (out) -> out.append("{}"); - assertThatIllegalArgumentException().isThrownBy(() -> writable.toOutputStream(null, StandardCharsets.UTF_8)) - .withMessage("'out' must not be null"); - } - - @Test - void toOutputStreamWithCharsetWhenCharsetIsNullThrowsException() { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - WritableJson writable = (out) -> out.append("{}"); - assertThatIllegalArgumentException().isThrownBy(() -> writable.toOutputStream(outputStream, null)) - .withMessage("'charset' must not be null"); - } - - // - - @Test - void toWriterWritesJson() throws Exception { - StringWriter writer = new StringWriter(); - WritableJson writable = (out) -> out.append("{}"); - writable.toWriter(writer); - assertThat(writer).hasToString("{}"); - } - - @Test - void toWriterWhenWriterIsNullThrowsException() { - WritableJson writable = (out) -> out.append("{}"); - assertThatIllegalArgumentException().isThrownBy(() -> writable.toWriter(null)) - .withMessage("'out' must not be null"); - } - - @Test - void ofReturnsInstanceWithSensibleToString() { - WritableJson writable = WritableJson.of((out) -> out.append("{}")); - assertThat(writable).hasToString("{}"); - } - - } - record Person(String firstName, String lastName, int age) { @Override diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/json/WritableJsonTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/json/WritableJsonTests.java new file mode 100644 index 0000000000..49ab5a499e --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/json/WritableJsonTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * 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 + * + * https://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 org.springframework.boot.json; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import org.springframework.core.io.FileSystemResource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link WritableJson}. + * + * @author Phillip Webb + * @author Moritz Halbritter + */ +class WritableJsonTests { + + @TempDir + File temp; + + @Test + void toJsonStringReturnsString() { + WritableJson writable = (out) -> out.append("{}"); + assertThat(writable.toJsonString()).isEqualTo("{}"); + } + + @Test + void toJsonStringWhenIOExceptionIsThrownThrowsUncheckedIOException() { + WritableJson writable = (out) -> { + throw new IOException("bad"); + }; + assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(() -> writable.toJsonString()) + .havingCause() + .withMessage("bad"); + } + + @Test + void toByteArrayReturnsByteArray() { + WritableJson writable = (out) -> out.append("{}"); + assertThat(writable.toByteArray()).isEqualTo("{}".getBytes()); + } + + @Test + void toResourceWritesJson() throws Exception { + File file = new File(this.temp, "out.json"); + WritableJson writable = (out) -> out.append("{}"); + writable.toResource(new FileSystemResource(file)); + assertThat(file).content().isEqualTo("{}"); + } + + @Test + void toResourceWithCharsetWritesJson() throws Exception { + File file = new File(this.temp, "out.json"); + WritableJson writable = (out) -> out.append("{}"); + writable.toResource(new FileSystemResource(file), StandardCharsets.ISO_8859_1); + assertThat(file).content(StandardCharsets.ISO_8859_1).isEqualTo("{}"); + } + + @Test + void toResourceWithCharsetWhenOutIsNullThrowsException() { + WritableJson writable = (out) -> out.append("{}"); + assertThatIllegalArgumentException().isThrownBy(() -> writable.toResource(null, StandardCharsets.UTF_8)) + .withMessage("'out' must not be null"); + } + + @Test + void toResourceWithCharsetWhenCharsetIsNullThrowsException() { + File file = new File(this.temp, "out.json"); + WritableJson writable = (out) -> out.append("{}"); + assertThatIllegalArgumentException().isThrownBy(() -> writable.toResource(new FileSystemResource(file), null)) + .withMessage("'charset' must not be null"); + } + + @Test + void toOutputStreamWritesJson() throws Exception { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + WritableJson writable = (out) -> out.append("{}"); + writable.toOutputStream(outputStream); + assertThat(outputStream.toString(StandardCharsets.UTF_8)).isEqualTo("{}"); + } + + @Test + void toOutputStreamWithCharsetWritesJson() throws Exception { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + WritableJson writable = (out) -> out.append("{}"); + writable.toOutputStream(outputStream, StandardCharsets.ISO_8859_1); + assertThat(outputStream.toString(StandardCharsets.ISO_8859_1)).isEqualTo("{}"); + } + + @Test + void toOutputStreamWithCharsetWhenOutIsNullThrowsException() { + WritableJson writable = (out) -> out.append("{}"); + assertThatIllegalArgumentException().isThrownBy(() -> writable.toOutputStream(null, StandardCharsets.UTF_8)) + .withMessage("'out' must not be null"); + } + + @Test + void toOutputStreamWithCharsetWhenCharsetIsNullThrowsException() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + WritableJson writable = (out) -> out.append("{}"); + assertThatIllegalArgumentException().isThrownBy(() -> writable.toOutputStream(outputStream, null)) + .withMessage("'charset' must not be null"); + } + + @Test + void toWriterWritesJson() throws Exception { + StringWriter writer = new StringWriter(); + WritableJson writable = (out) -> out.append("{}"); + writable.toWriter(writer); + assertThat(writer).hasToString("{}"); + } + + @Test + void toWriterWhenWriterIsNullThrowsException() { + WritableJson writable = (out) -> out.append("{}"); + assertThatIllegalArgumentException().isThrownBy(() -> writable.toWriter(null)) + .withMessage("'out' must not be null"); + } + + @Test + void ofReturnsInstanceWithSensibleToString() { + WritableJson writable = WritableJson.of((out) -> out.append("{}")); + assertThat(writable).hasToString("{}"); + } + +}