This is an automated email from the ASF dual-hosted git repository. paulk pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 978816738bcaa1f6ddc9e36b3d30abf666d060e2 Author: Paul King <[email protected]> AuthorDate: Sun Apr 12 15:41:54 2026 +1000 GROOVY-11925: Improve consistency of TOML functionality --- .../src/main/java/groovy/toml/TomlBuilder.java | 17 +++++ .../src/main/java/groovy/toml/TomlSlurper.java | 73 ++++++++++++++++++++++ .../groovy-toml/src/spec/doc/toml-userguide.adoc | 25 ++++++++ .../spec/test/groovy/toml/TomlBuilderTest.groovy | 24 +++++++ .../spec/test/groovy/toml/TomlParserTest.groovy | 48 ++++++++++++++ 5 files changed, 187 insertions(+) diff --git a/subprojects/groovy-toml/src/main/java/groovy/toml/TomlBuilder.java b/subprojects/groovy-toml/src/main/java/groovy/toml/TomlBuilder.java index a8416a8ae4..c6db8e5599 100644 --- a/subprojects/groovy-toml/src/main/java/groovy/toml/TomlBuilder.java +++ b/subprojects/groovy-toml/src/main/java/groovy/toml/TomlBuilder.java @@ -18,6 +18,7 @@ */ package groovy.toml; +import com.fasterxml.jackson.dataformat.toml.TomlMapper; import groovy.json.JsonBuilder; import groovy.lang.Closure; import groovy.lang.GroovyObjectSupport; @@ -45,6 +46,22 @@ public class TomlBuilder extends GroovyObjectSupport implements Writable { this.jsonBuilder = new JsonBuilder(); } + /** + * Serializes a typed object to a TOML string using Jackson databinding. + * Supports {@code @JsonProperty} and {@code @JsonFormat} annotations. + * + * @param object the object to serialize + * @return the TOML string + * @since 6.0.0 + */ + public static String toToml(Object object) { + try { + return new TomlMapper().writeValueAsString(object); + } catch (IOException e) { + throw new TomlRuntimeException(e); + } + } + public Object getContent() { return jsonBuilder.getContent(); } diff --git a/subprojects/groovy-toml/src/main/java/groovy/toml/TomlSlurper.java b/subprojects/groovy-toml/src/main/java/groovy/toml/TomlSlurper.java index ee998284be..4e191215f3 100644 --- a/subprojects/groovy-toml/src/main/java/groovy/toml/TomlSlurper.java +++ b/subprojects/groovy-toml/src/main/java/groovy/toml/TomlSlurper.java @@ -18,10 +18,12 @@ */ package groovy.toml; +import com.fasterxml.jackson.dataformat.toml.TomlMapper; import groovy.json.JsonSlurper; import org.apache.groovy.lang.annotation.Incubating; import org.apache.groovy.toml.util.TomlConverter; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -93,4 +95,75 @@ public class TomlSlurper { // note: convert to an input stream to allow the support of foreign file objects return parse(Files.newInputStream(path)); } + + /** + * Parse the content of the specified TOML text into a typed object using Jackson databinding. + * Supports {@code @JsonProperty} and {@code @JsonFormat} annotations for + * property mapping and type conversion. + * + * @param type the target type + * @param toml the content of TOML + * @param <T> the target type + * @return a typed object + * @since 6.0.0 + */ + public <T> T parseTextAs(Class<T> type, String toml) { + return parseAs(type, new StringReader(toml)); + } + + /** + * Parse TOML from a reader into a typed object. + * + * @param type the target type + * @param reader the reader of TOML + * @param <T> the target type + * @return a typed object + * @since 6.0.0 + */ + public <T> T parseAs(Class<T> type, Reader reader) { + try (Reader r = reader) { + return new TomlMapper().readValue(r, type); + } catch (IOException e) { + throw new TomlRuntimeException(e); + } + } + + /** + * Parse TOML from an input stream into a typed object. + * + * @param type the target type + * @param stream the input stream of TOML + * @param <T> the target type + * @return a typed object + * @since 6.0.0 + */ + public <T> T parseAs(Class<T> type, InputStream stream) { + return parseAs(type, new InputStreamReader(stream)); + } + + /** + * Parse TOML from a file into a typed object. + * + * @param type the target type + * @param file the TOML file + * @param <T> the target type + * @return a typed object + * @since 6.0.0 + */ + public <T> T parseAs(Class<T> type, File file) throws IOException { + return parseAs(type, file.toPath()); + } + + /** + * Parse TOML from a path into a typed object. + * + * @param type the target type + * @param path the path to the TOML file + * @param <T> the target type + * @return a typed object + * @since 6.0.0 + */ + public <T> T parseAs(Class<T> type, Path path) throws IOException { + return parseAs(type, Files.newInputStream(path)); + } } diff --git a/subprojects/groovy-toml/src/spec/doc/toml-userguide.adoc b/subprojects/groovy-toml/src/spec/doc/toml-userguide.adoc index d59ab1935d..a160bc857c 100644 --- a/subprojects/groovy-toml/src/spec/doc/toml-userguide.adoc +++ b/subprojects/groovy-toml/src/spec/doc/toml-userguide.adoc @@ -90,6 +90,22 @@ The following table gives an overview of the TOML types and the corresponding Gr Whenever a value in TOML is `null`, `TomlSlurper` supplements it with the Groovy `null` value. This is in contrast to other TOML parsers that represent a `null` value with a library-provided singleton object. +=== Typed parsing + +`TomlSlurper` can parse TOML directly into typed objects using Jackson databinding. +Standard Jackson annotations such as `@JsonProperty` and `@JsonFormat` are supported +for property mapping and type conversion: + +[source,groovy] +---- +include::../test/groovy/toml/TomlParserTest.groovy[tags=typed_class,indent=0] +---- + +[source,groovy] +---- +include::../test/groovy/toml/TomlParserTest.groovy[tags=typed_parsing,indent=0] +---- + === Builders Another way to create TOML from Groovy is to use `TomlBuilder`. The builder provide a @@ -99,3 +115,12 @@ DSL which allows to formulate an object graph which is then converted to TOML. ---- include::../test/groovy/toml/TomlBuilderTest.groovy[tags=build_text,indent=0] ---- + +=== Typed writing + +`TomlBuilder.toToml(object)` serializes a typed object directly to TOML using Jackson databinding: + +[source,groovy] +---- +include::../test/groovy/toml/TomlBuilderTest.groovy[tags=typed_writing,indent=0] +---- diff --git a/subprojects/groovy-toml/src/spec/test/groovy/toml/TomlBuilderTest.groovy b/subprojects/groovy-toml/src/spec/test/groovy/toml/TomlBuilderTest.groovy index e338b3c447..8bca0bc248 100644 --- a/subprojects/groovy-toml/src/spec/test/groovy/toml/TomlBuilderTest.groovy +++ b/subprojects/groovy-toml/src/spec/test/groovy/toml/TomlBuilderTest.groovy @@ -50,4 +50,28 @@ records.car.record.description = 'production pickup truck with speed of 271kph' ''' // end::build_text[] } + + // tag::typed_writing[] + static class ServerConfig { + String host + int port + } + + void testToToml() { + def config = new ServerConfig(host: 'localhost', port: 8080) + def toml = TomlBuilder.toToml(config) + assert toml.contains('host') + assert toml.contains('localhost') + assert toml.contains('port') + assert toml.contains('8080') + } + // end::typed_writing[] + + void testTypedRoundTrip() { + def original = new ServerConfig(host: 'example.com', port: 443) + def toml = TomlBuilder.toToml(original) + def parsed = new TomlSlurper().parseTextAs(ServerConfig, toml) + assert parsed.host == 'example.com' + assert parsed.port == 443 + } } \ No newline at end of file diff --git a/subprojects/groovy-toml/src/spec/test/groovy/toml/TomlParserTest.groovy b/subprojects/groovy-toml/src/spec/test/groovy/toml/TomlParserTest.groovy index dacef27779..7bcaaeb329 100644 --- a/subprojects/groovy-toml/src/spec/test/groovy/toml/TomlParserTest.groovy +++ b/subprojects/groovy-toml/src/spec/test/groovy/toml/TomlParserTest.groovy @@ -107,4 +107,52 @@ jdk = "oraclejdk8" assert ['openjdk10', 'oraclejdk9', 'oraclejdk8'] == toml.matrix.include.jdk } + + // tag::typed_class[] + static class ServerConfig { + String host + int port + } + // end::typed_class[] + + void testParseTextAs() { + // tag::typed_parsing[] + def config = new TomlSlurper().parseTextAs(ServerConfig, ''' +host = "localhost" +port = 8080 +''') + assert config.host == 'localhost' + assert config.port == 8080 + // end::typed_parsing[] + } + + void testParseAsFromReader() { + def reader = new StringReader('host = "localhost"\nport = 8080') + def config = new TomlSlurper().parseAs(ServerConfig, reader) + assert config instanceof ServerConfig + assert config.host == 'localhost' + assert config.port == 8080 + } + + void testParseAsFromFile() { + def file = File.createTempFile('test', '.toml') + file.deleteOnExit() + file.text = 'host = "localhost"\nport = 9090' + def config = new TomlSlurper().parseAs(ServerConfig, file) + assert config.port == 9090 + } + + void testParseAsFromPath() { + def file = File.createTempFile('test', '.toml') + file.deleteOnExit() + file.text = 'host = "example.com"\nport = 443' + def config = new TomlSlurper().parseAs(ServerConfig, file.toPath()) + assert config.host == 'example.com' + } + + void testParseAsFromInputStream() { + def stream = new ByteArrayInputStream('host = "localhost"\nport = 3000'.bytes) + def config = new TomlSlurper().parseAs(ServerConfig, stream) + assert config.port == 3000 + } }
