You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/12/13 09:56:00 UTC

[isis] branch master updated: ISIS-3304: promote Json utils from internal to public

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new df0f7e23bb ISIS-3304: promote Json utils from internal to public
df0f7e23bb is described below

commit df0f7e23bbfc786b9a4fdf42f17caf2ec0898412
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Dec 13 10:55:49 2022 +0100

    ISIS-3304: promote Json utils from internal to public
    
    - introduces general purpose data-sink and data source to consolidate
    common Input-/OutputStream idioms
---
 .../applib/util/schema/CommonDtoUtils.java         |  28 ++-
 commons/src/main/java/module-info.java             |   1 +
 .../causeway/commons/internal/resources/_Json.java | 246 ---------------------
 .../org/apache/causeway/commons/io/DataSink.java   |  71 ++++++
 .../org/apache/causeway/commons/io/DataSource.java |  87 ++++++++
 .../org/apache/causeway/commons/io/JsonUtils.java  | 133 +++++++++++
 .../commons/resource/ResourceCoordinates.java      |   3 +-
 .../internal/resources/JsonYamlReaderTest.java     |  13 +-
 .../restclient/api/OutboxClient.java               |   4 +-
 .../pdfjs/applib/config/PdfJsConfig.java           |   4 +-
 .../valuetypes/vega/applib/value/Vega.java         |   6 +-
 .../restfulobjects/applib/JsonRepresentation.java  |  71 +++---
 .../serialization/SerializationStrategy.java       |  12 +-
 13 files changed, 362 insertions(+), 317 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/causeway/applib/util/schema/CommonDtoUtils.java b/api/applib/src/main/java/org/apache/causeway/applib/util/schema/CommonDtoUtils.java
index d3d98d6c6c..e18e481160 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/util/schema/CommonDtoUtils.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/util/schema/CommonDtoUtils.java
@@ -44,7 +44,8 @@ import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.commons.internal.context._Context;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
-import org.apache.causeway.commons.internal.resources._Json;
+import org.apache.causeway.commons.io.DataSource;
+import org.apache.causeway.commons.io.JsonUtils;
 import org.apache.causeway.schema.cmd.v2.MapDto;
 import org.apache.causeway.schema.cmd.v2.ParamDto;
 import org.apache.causeway.schema.common.v2.BlobDto;
@@ -161,17 +162,23 @@ public final class CommonDtoUtils {
             return valueDto;
         }
         case ENUM: {
-            final EnumDto enumDto = _Json.readJson(EnumDto.class, json).getValue().orElseThrow();
+            final EnumDto enumDto = JsonUtils.tryRead(EnumDto.class, DataSource.ofStringUtf8(json))
+                    .ifFailureFail()
+                    .getValue().orElseThrow();
             valueDto.setEnum(enumDto);
             return valueDto;
         }
         case BLOB: {
-            final BlobDto blobDto = _Json.readJson(BlobDto.class, json).getValue().orElseThrow();
+            final BlobDto blobDto = JsonUtils.tryRead(BlobDto.class, DataSource.ofStringUtf8(json))
+                    .ifFailureFail()
+                    .getValue().orElseThrow();
             valueDto.setBlob(blobDto);
             return valueDto;
         }
         case CLOB: {
-            final ClobDto clobDto = _Json.readJson(ClobDto.class, json).getValue().orElseThrow();
+            final ClobDto clobDto = JsonUtils.tryRead(ClobDto.class, DataSource.ofStringUtf8(json))
+                    .ifFailureFail()
+                    .getValue().orElseThrow();
             valueDto.setClob(clobDto);
             return valueDto;
         }
@@ -256,25 +263,24 @@ public final class CommonDtoUtils {
 
     @Nullable
     public String getCompositeValueAsJson(final @Nullable TypedTupleDto composite) {
-        return composite!=null
-            ? _Json.toString(
+        return JsonUtils.toStringUtf8(
                 composite,
-                _Json::jaxbAnnotationSupport,
-                _Json::onlyIncludeNonNull)
-            : null;
+                JsonUtils::jaxbAnnotationSupport,
+                JsonUtils::onlyIncludeNonNull);
     }
 
     @SneakyThrows
     @Nullable
     public TypedTupleDto getCompositeValueFromJson(final @Nullable String json) {
         return _Strings.isNotEmpty(json)
-                ? _Json.readJson(TypedTupleDto.class, json, _Json::jaxbAnnotationSupport)
+                ? JsonUtils.tryRead(TypedTupleDto.class, DataSource.ofStringUtf8(json), JsonUtils::jaxbAnnotationSupport)
+                        .ifFailureFail()
                         .getValue().orElseThrow()
                 : null;
     }
 
     private String dtoToJson(final @Nullable Object dto) {
-        return _Json.toString(dto);
+        return JsonUtils.toStringUtf8(dto);
     }
 
     // -- VALUE RECORD
diff --git a/commons/src/main/java/module-info.java b/commons/src/main/java/module-info.java
index 51d62347e4..95db876e40 100644
--- a/commons/src/main/java/module-info.java
+++ b/commons/src/main/java/module-info.java
@@ -24,6 +24,7 @@ module org.apache.causeway.commons {
     exports org.apache.causeway.commons.handler;
     exports org.apache.causeway.commons.having;
     exports org.apache.causeway.commons.resource;
+    exports org.apache.causeway.commons.io;
     exports org.apache.causeway.commons.internal;
     exports org.apache.causeway.commons.internal.assertions;
     exports org.apache.causeway.commons.internal.base;
diff --git a/commons/src/main/java/org/apache/causeway/commons/internal/resources/_Json.java b/commons/src/main/java/org/apache/causeway/commons/internal/resources/_Json.java
deleted file mode 100644
index d858c8748a..0000000000
--- a/commons/src/main/java/org/apache/causeway/commons/internal/resources/_Json.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.apache.causeway.commons.internal.resources;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import java.util.function.UnaryOperator;
-
-import com.fasterxml.jackson.annotation.JsonInclude.Include;
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
-
-import org.springframework.lang.Nullable;
-
-import org.apache.causeway.commons.functional.Try;
-
-import lombok.SneakyThrows;
-import lombok.val;
-
-/**
- * <h1>- internal use only -</h1>
- * <p>
- * Utilities for the JSON format.
- * </p>
- * <p>
- * <b>WARNING</b>: Do <b>NOT</b> use any of the classes provided by this package! <br/>
- * These may be changed or removed without notice!
- * </p>
- * @since 2.0
- */
-public class _Json {
-
-    // -- STREAM CONTENT
-
-    private static <T> T _readJson(final Class<T> clazz, final InputStream content)
-            throws JsonParseException, JsonMappingException, IOException {
-
-        return new ObjectMapper().readValue(content, clazz);
-    }
-
-    /**
-     * Either deserialize JSON content from given JSON content InputStream into an instance of
-     * given {@code clazz} type, or any exception that occurred during parsing.
-     * @param <T>
-     * @param clazz
-     * @param content
-     */
-    public static <T> Try<T> readJson(final Class<T> clazz, final InputStream content) {
-        return Try.call(()->_readJson(clazz, content));
-    }
-
-    private static <T> List<T> _readJsonList(final Class<T> elementType, final InputStream content)
-            throws JsonParseException, JsonMappingException, IOException {
-
-        val mapper = new ObjectMapper();
-        val listFactory = mapper.getTypeFactory().constructCollectionType(List.class, elementType);
-        return mapper.readValue(content, listFactory);
-    }
-
-    /**
-     * Either deserialize JSON content from given JSON content InputStream into an instance of List
-     * with given {@code elementType}, or any exception that occurred during parsing.
-     * @param <T>
-     * @param clazz
-     * @param content
-     */
-    public static <T> Try<List<T>> readJsonList(final Class<T> clazz, final InputStream content) {
-        return Try.call(()->_readJsonList(clazz, content));
-    }
-
-
-    // -- STRING CONTENT
-
-    /**
-     * Either deserialize JSON content from given JSON content String into an instance of
-     * given {@code clazz} type, or any exception that occurred during parsing.
-     * @param <T>
-     * @param clazz
-     * @param content
-     */
-    public static <T> Try<T> readJson(
-            final Class<T> clazz,
-            final String content,
-            final JsonCustomizer ... customizers) {
-        return Try.call(()->mapper(customizers).readValue(content, clazz));
-    }
-
-    private static <T> List<T> _readJsonList(final Class<T> elementType, final String content)
-            throws JsonParseException, JsonMappingException, IOException {
-
-        val mapper = new ObjectMapper();
-        val listFactory = mapper.getTypeFactory().constructCollectionType(List.class, elementType);
-        return mapper.readValue(content, listFactory);
-    }
-
-    /**
-     * Either deserialize JSON content from given JSON content String into an instance of List
-     * with given {@code elementType}, or any exception that occurred during parsing.
-     * @param <T>
-     * @param clazz
-     * @param content
-     */
-    public static <T> Try<List<T>> readJsonList(final Class<T> clazz, final String content) {
-        return Try.call(()->_readJsonList(clazz, content));
-    }
-
-
-    // -- FILE CONTENT
-
-    private static <T> T _readJson(final Class<T> clazz, final File content)
-            throws JsonParseException, JsonMappingException, IOException {
-
-        return new ObjectMapper().readValue(content, clazz);
-    }
-
-    /**
-     * Either deserialize JSON content from given JSON content File into an instance of
-     * given {@code clazz} type, or any exception that occurred during parsing.
-     * @param <T>
-     * @param clazz
-     * @param content
-     */
-    public static <T> Try<T> readJson(final Class<T> clazz, final File content) {
-        return Try.call(()->_readJson(clazz, content));
-    }
-
-    private static <T> List<T> _readJsonList(final Class<T> elementType, final File content)
-            throws JsonParseException, JsonMappingException, IOException {
-
-        val mapper = new ObjectMapper();
-        val listFactory = mapper.getTypeFactory().constructCollectionType(List.class, elementType);
-        return mapper.readValue(content, listFactory);
-    }
-
-    /**
-     * Either deserialize JSON content from given JSON content File into an instance of List
-     * with given {@code elementType}, or any exception that occurred during parsing.
-     * @param <T>
-     * @param clazz
-     * @param content
-     */
-    public static <T> Try<List<T>> readJsonList(final Class<T> clazz, final File content) {
-        return Try.call(()->_readJsonList(clazz, content));
-    }
-
-    // -- BYTE CONTENT
-
-    private static <T> T _readJson(final Class<T> clazz, final byte[] content)
-            throws JsonParseException, JsonMappingException, IOException {
-
-        return new ObjectMapper().readValue(content, clazz);
-    }
-
-    /**
-     * Either deserialize JSON content from given JSON content byte[] into an instance of
-     * given {@code clazz} type, or any exception that occurred during parsing.
-     * @param <T>
-     * @param clazz
-     * @param content
-     */
-    public static <T> Try<T> readJson(final Class<T> clazz, final byte[] content) {
-        return Try.call(()->_readJson(clazz, content));
-    }
-
-    private static <T> List<T> _readJsonList(final Class<T> elementType, final byte[] content)
-            throws JsonParseException, JsonMappingException, IOException {
-
-        val mapper = new ObjectMapper();
-        val listFactory = mapper.getTypeFactory().constructCollectionType(List.class, elementType);
-        return mapper.readValue(content, listFactory);
-    }
-
-    /**
-     * Either deserialize JSON content from given JSON content byte[] into an instance of List
-     * with given {@code elementType}, or any exception that occurred during parsing.
-     * @param <T>
-     * @param clazz
-     * @param content
-     */
-    public static <T> Try<List<T>> readJsonList(final Class<T> clazz, final byte[] content) {
-        return Try.call(()->_readJsonList(clazz, content));
-    }
-
-    // -- WRITING
-
-    @FunctionalInterface
-    public static interface JsonCustomizer extends UnaryOperator<ObjectMapper> {};
-
-    /** enable indentation for the underlying generator */
-    public static ObjectMapper indentedOutput(final ObjectMapper mapper) {
-        return mapper.enable(SerializationFeature.INDENT_OUTPUT);
-    }
-
-    /** only properties with non-null values are to be included */
-    public static ObjectMapper onlyIncludeNonNull(final ObjectMapper mapper) {
-        return mapper.setSerializationInclusion(Include.NON_NULL);
-    }
-
-    /** add support for JAXB annotations */
-    public static ObjectMapper jaxbAnnotationSupport(final ObjectMapper mapper) {
-        return mapper.registerModule(new JaxbAnnotationModule());
-    }
-
-    @SneakyThrows
-    @Nullable
-    public static String toString(
-            final @Nullable Object pojo,
-            final JsonCustomizer ... customizers) {
-        return pojo!=null
-                ? mapper(customizers).writeValueAsString(pojo)
-                : null;
-    }
-
-    // -- GET MAPPER
-
-    private static ObjectMapper mapper(
-            final JsonCustomizer ... customizers) {
-        var mapper = new ObjectMapper();
-        for(JsonCustomizer customizer : customizers) {
-            mapper = customizer.apply(mapper);
-        }
-        return mapper;
-    }
-
-}
diff --git a/commons/src/main/java/org/apache/causeway/commons/io/DataSink.java b/commons/src/main/java/org/apache/causeway/commons/io/DataSink.java
new file mode 100644
index 0000000000..9bf26b4a03
--- /dev/null
+++ b/commons/src/main/java/org/apache/causeway/commons/io/DataSink.java
@@ -0,0 +1,71 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.causeway.commons.io;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.apache.causeway.commons.functional.Try;
+
+import lombok.NonNull;
+
+/**
+ * General purpose writable byte data sink.
+ *
+ * @since 2.0 {@index}
+ */
+@FunctionalInterface
+public interface DataSink {
+
+    /**
+     * Re-throws any {@link Exception} from the mapped {@link Try},
+     * when the Try is a failure case.
+     */
+    void writeAll(@NonNull Function<OutputStream, Try<Void>> consumingMapper);
+
+    // -- FACTORIES
+
+    /**
+     * Acts as a no-op.
+     */
+    static DataSink none() {
+        return consumingMapper -> {};
+    }
+
+    static DataSink ofOutputStreamSupplier(final @NonNull Supplier<OutputStream> outputStreamSupplier) {
+        return outputConsumer ->
+            Try.call(()->{
+                try(final OutputStream os = outputStreamSupplier.get()) {
+                    return outputConsumer.apply(os);
+                }
+            })
+            .ifFailureFail() // throw if any Exception outside the call to 'outputConsumer.apply(os)'
+            // unwrap the inner Try<Void>
+            .getValue().orElseThrow()
+            .ifFailureFail(); // throw if any Exception within the call to 'outputConsumer.apply(os)'
+    }
+
+    static DataSink ofFile(final @NonNull File file) {
+        return ofOutputStreamSupplier(()->Try.call(()->new FileOutputStream(file)).ifFailureFail().getValue().orElseThrow());
+    }
+
+}
diff --git a/commons/src/main/java/org/apache/causeway/commons/io/DataSource.java b/commons/src/main/java/org/apache/causeway/commons/io/DataSource.java
new file mode 100644
index 0000000000..153fa10290
--- /dev/null
+++ b/commons/src/main/java/org/apache/causeway/commons/io/DataSource.java
@@ -0,0 +1,87 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.causeway.commons.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.apache.causeway.commons.functional.Try;
+
+import lombok.NonNull;
+
+/**
+ * General purpose readable byte data source.
+ *
+ * @since 2.0 {@index}
+ */
+@FunctionalInterface
+public interface DataSource {
+
+    <T> Try<T> readAll(@NonNull Function<InputStream, Try<T>> consumingMapper);
+
+    // -- FACTORIES
+
+    /**
+     * Acts as a no-op.
+     */
+    static DataSource none() {
+        return new DataSource() {
+            @Override public <T> Try<T> readAll(final @NonNull Function<InputStream, Try<T>> consumingMapper) {
+                return Try.success(null);
+            }
+        };
+    }
+
+    static DataSource fromInputStreamSupplier(final @NonNull Supplier<InputStream> inputStreamSupplier) {
+        return new DataSource() {
+            @Override public <T> Try<T> readAll(final @NonNull Function<InputStream, Try<T>> consumingMapper) {
+                return Try.call(()->{
+                    try(final InputStream is = inputStreamSupplier.get()) {
+                        return consumingMapper.apply(is);
+                    }
+                })
+                // unwrap the inner try
+                .mapSuccess(wrappedTry->wrappedTry.getValue().get());
+            }
+        };
+    }
+
+    static DataSource ofResource(final @NonNull Class<?> cls, final @NonNull String resourcePath) {
+        return fromInputStreamSupplier(()->cls.getResourceAsStream(resourcePath));
+    }
+
+    static DataSource ofFile(final @NonNull File file) {
+        return fromInputStreamSupplier(()->Try.call(()->new FileInputStream(file)).ifFailureFail().getValue().orElseThrow());
+    }
+
+    static DataSource ofString(final @NonNull String string, final Charset charset) {
+        return fromInputStreamSupplier(()->new ByteArrayInputStream(string.getBytes(charset)));
+    }
+
+    static DataSource ofStringUtf8(final @NonNull String string) {
+        return ofString(string, StandardCharsets.UTF_8);
+    }
+
+}
diff --git a/commons/src/main/java/org/apache/causeway/commons/io/JsonUtils.java b/commons/src/main/java/org/apache/causeway/commons/io/JsonUtils.java
new file mode 100644
index 0000000000..d6828ef972
--- /dev/null
+++ b/commons/src/main/java/org/apache/causeway/commons/io/JsonUtils.java
@@ -0,0 +1,133 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.causeway.commons.io;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.function.UnaryOperator;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
+
+import org.springframework.lang.Nullable;
+
+import org.apache.causeway.commons.functional.Try;
+
+import lombok.NonNull;
+import lombok.SneakyThrows;
+import lombok.val;
+import lombok.experimental.UtilityClass;
+
+/**
+ * Utilities to convert from and to JSON format.
+ *
+ * @since 2.0 {@index}
+ */
+@UtilityClass
+public class JsonUtils {
+
+    @FunctionalInterface
+    public interface JsonCustomizer extends UnaryOperator<ObjectMapper> {}
+
+    // -- READING
+
+    /**
+     * Tries to deserialize JSON content from given {@link DataSource} into an instance of
+     * given {@code requiredType}.
+     */
+    public <T> Try<T> tryRead(
+            final @NonNull Class<T> requiredType,
+            final @NonNull DataSource source,
+            final JsonUtils.JsonCustomizer ... customizers) {
+        return source.readAll((final InputStream is)->{
+            return Try.call(()->createMapper(customizers).readValue(is, requiredType));
+        });
+    }
+
+    /**
+     * Tries to deserialize JSON content from given {@link DataSource} into a {@link List}
+     * with given {@code elementType}.
+     */
+    public <T> Try<List<T>> tryReadAsList(
+            final @NonNull Class<T> elementType,
+            final @NonNull DataSource source,
+            final JsonUtils.JsonCustomizer ... customizers) {
+        return source.readAll((final InputStream is)->{
+            return Try.call(()->{
+                val mapper = createMapper(customizers);
+                val listFactory = mapper.getTypeFactory().constructCollectionType(List.class, elementType);
+                return mapper.readValue(is, listFactory);
+            });
+        });
+    }
+
+    // -- WRITING
+
+    public void write(
+            final @Nullable Object pojo,
+            final @NonNull DataSink sink,
+            final JsonUtils.JsonCustomizer ... customizers) {
+        if(pojo==null) return;
+        sink.writeAll(os->
+            Try.run(()->createMapper(customizers).writeValue(os, sink)));
+    }
+
+    @SneakyThrows
+    @Nullable
+    public static String toStringUtf8(
+            final @Nullable Object pojo,
+            final JsonUtils.JsonCustomizer ... customizers) {
+        return pojo!=null
+                ? createMapper(customizers).writeValueAsString(pojo)
+                : null;
+    }
+
+    // -- CUSTOMIZERS
+
+    /** enable indentation for the underlying generator */
+    public ObjectMapper indentedOutput(final ObjectMapper mapper) {
+        return mapper.enable(SerializationFeature.INDENT_OUTPUT);
+    }
+
+    /** only properties with non-null values are to be included */
+    public ObjectMapper onlyIncludeNonNull(final ObjectMapper mapper) {
+        return mapper.setSerializationInclusion(Include.NON_NULL);
+    }
+
+    /** add support for JAXB annotations */
+    public ObjectMapper jaxbAnnotationSupport(final ObjectMapper mapper) {
+        return mapper.registerModule(new JaxbAnnotationModule());
+    }
+
+    // -- MAPPER FACTORY
+
+    private ObjectMapper createMapper(
+            final JsonUtils.JsonCustomizer ... customizers) {
+        var mapper = new ObjectMapper();
+        for(JsonUtils.JsonCustomizer customizer : customizers) {
+            mapper = customizer.apply(mapper);
+        }
+        return mapper;
+    }
+
+
+
+}
diff --git a/commons/src/main/java/org/apache/causeway/commons/resource/ResourceCoordinates.java b/commons/src/main/java/org/apache/causeway/commons/resource/ResourceCoordinates.java
index 73729f2477..31a4c28cff 100644
--- a/commons/src/main/java/org/apache/causeway/commons/resource/ResourceCoordinates.java
+++ b/commons/src/main/java/org/apache/causeway/commons/resource/ResourceCoordinates.java
@@ -34,8 +34,9 @@ import lombok.Value;
 import lombok.val;
 
 /**
- * @since 2.0 {@index}
+ * @deprecated move to tooling
  */
+@Deprecated
 @Value @Builder
 public class ResourceCoordinates
 implements Comparable<ResourceCoordinates> {
diff --git a/commons/src/test/java/org/apache/causeway/commons/internal/resources/JsonYamlReaderTest.java b/commons/src/test/java/org/apache/causeway/commons/internal/resources/JsonYamlReaderTest.java
index 86903ee9da..ff8143c708 100644
--- a/commons/src/test/java/org/apache/causeway/commons/internal/resources/JsonYamlReaderTest.java
+++ b/commons/src/test/java/org/apache/causeway/commons/internal/resources/JsonYamlReaderTest.java
@@ -30,6 +30,9 @@ import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
+import org.apache.causeway.commons.io.DataSource;
+import org.apache.causeway.commons.io.JsonUtils;
+
 import lombok.Data;
 import lombok.val;
 
@@ -52,8 +55,10 @@ class JsonYamlReaderTest {
 
     @Test
     void loadCustomerFromJson() throws JsonParseException, JsonMappingException, IOException {
-        val customer = _Json.readJson(Customer.class, this.getClass().getResourceAsStream("customer.json"))
-                .getValue().orElse(null);
+        val customer = JsonUtils.tryRead(Customer.class, DataSource.ofResource(this.getClass(), "customer.json"))
+                .ifFailureFail()
+                .getValue()
+                .orElse(null);
         assertCustomerIsJohnDoe(customer);
     }
 
@@ -63,10 +68,10 @@ class JsonYamlReaderTest {
                 .getValue().orElse(null);
         assertCustomerIsJohnDoe(customer);
     }
-    
+
     // -- HELPER
 
-    private void assertCustomerIsJohnDoe(Customer customer) {
+    private void assertCustomerIsJohnDoe(final Customer customer) {
         assertNotNull(customer);
         assertEquals("John", customer.getFirstName());
         assertEquals("Doe", customer.getLastName());
diff --git a/extensions/core/executionoutbox/restclient/src/main/java/org/apache/causeway/extensions/executionoutbox/restclient/api/OutboxClient.java b/extensions/core/executionoutbox/restclient/src/main/java/org/apache/causeway/extensions/executionoutbox/restclient/api/OutboxClient.java
index 27bc33f225..d4c65fe8c1 100644
--- a/extensions/core/executionoutbox/restclient/src/main/java/org/apache/causeway/extensions/executionoutbox/restclient/api/OutboxClient.java
+++ b/extensions/core/executionoutbox/restclient/src/main/java/org/apache/causeway/extensions/executionoutbox/restclient/api/OutboxClient.java
@@ -28,7 +28,7 @@ import javax.ws.rs.core.MediaType;
 
 import org.apache.causeway.applib.util.schema.InteractionsDtoUtils;
 import org.apache.causeway.commons.functional.Try;
-import org.apache.causeway.commons.internal.resources._Json;
+import org.apache.causeway.commons.io.JsonUtils;
 import org.apache.causeway.extensions.executionoutbox.restclient.api.delete.DeleteMessage;
 import org.apache.causeway.extensions.executionoutbox.restclient.api.deleteMany.DeleteManyMessage;
 import org.apache.causeway.schema.common.v2.InteractionType;
@@ -186,7 +186,7 @@ public class OutboxClient {
             var invocationBuilder = client.request(path);
 
             val invocation = invocationBuilder.buildPut(
-                    Entity.entity(_Json.toString(dto), MediaType.APPLICATION_JSON_TYPE));
+                    Entity.entity(JsonUtils.toStringUtf8(dto), MediaType.APPLICATION_JSON_TYPE));
 
             val response = invocation.invoke();
 
diff --git a/extensions/vw/pdfjs/applib/src/main/java/org/apache/causeway/extensions/pdfjs/applib/config/PdfJsConfig.java b/extensions/vw/pdfjs/applib/src/main/java/org/apache/causeway/extensions/pdfjs/applib/config/PdfJsConfig.java
index e3d7201305..aaba1191f9 100644
--- a/extensions/vw/pdfjs/applib/src/main/java/org/apache/causeway/extensions/pdfjs/applib/config/PdfJsConfig.java
+++ b/extensions/vw/pdfjs/applib/src/main/java/org/apache/causeway/extensions/pdfjs/applib/config/PdfJsConfig.java
@@ -22,7 +22,7 @@ import java.io.Serializable;
 
 import org.springframework.util.Assert;
 
-import org.apache.causeway.commons.internal.resources._Json;
+import org.apache.causeway.commons.io.JsonUtils;
 
 import lombok.AllArgsConstructor;
 import lombok.Builder;
@@ -81,7 +81,7 @@ public class PdfJsConfig implements Serializable {
     }
 
     public String toJsonString() {
-        return _Json.toString(this);
+        return JsonUtils.toStringUtf8(this);
     }
 
 }
diff --git a/valuetypes/vega/applib/src/main/java/org/apache/causeway/valuetypes/vega/applib/value/Vega.java b/valuetypes/vega/applib/src/main/java/org/apache/causeway/valuetypes/vega/applib/value/Vega.java
index 3eaf7f6016..32b9ca82db 100644
--- a/valuetypes/vega/applib/src/main/java/org/apache/causeway/valuetypes/vega/applib/value/Vega.java
+++ b/valuetypes/vega/applib/src/main/java/org/apache/causeway/valuetypes/vega/applib/value/Vega.java
@@ -28,7 +28,8 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 import org.springframework.lang.Nullable;
 
 import org.apache.causeway.commons.internal.base._Strings;
-import org.apache.causeway.commons.internal.resources._Json;
+import org.apache.causeway.commons.io.DataSource;
+import org.apache.causeway.commons.io.JsonUtils;
 import org.apache.causeway.valuetypes.vega.applib.CausewayModuleValVegaApplib;
 import org.apache.causeway.valuetypes.vega.applib.jaxb.VegaJaxbAdapter;
 
@@ -73,7 +74,8 @@ public final class Vega implements Serializable {
          * parses the json input for schema specification
          */
         @NonNull static Schema valueOfJson(final @Nullable String json) {
-            return _Json.readJson(Map.class, json).getValue()
+            return JsonUtils.tryRead(Map.class, DataSource.ofStringUtf8(json))
+                    .getValue()
             .map(map->map.get(key()))
             .map(schemaValue->{
                 for(var schema:Schema.values()) {
diff --git a/viewers/restfulobjects/applib/src/main/java/org/apache/causeway/viewer/restfulobjects/applib/JsonRepresentation.java b/viewers/restfulobjects/applib/src/main/java/org/apache/causeway/viewer/restfulobjects/applib/JsonRepresentation.java
index 18b61917af..4154afe844 100644
--- a/viewers/restfulobjects/applib/src/main/java/org/apache/causeway/viewer/restfulobjects/applib/JsonRepresentation.java
+++ b/viewers/restfulobjects/applib/src/main/java/org/apache/causeway/viewer/restfulobjects/applib/JsonRepresentation.java
@@ -52,7 +52,8 @@ import org.apache.causeway.commons.internal.base._Casts;
 import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.commons.internal.collections._Maps;
-import org.apache.causeway.commons.internal.resources._Json;
+import org.apache.causeway.commons.io.DataSource;
+import org.apache.causeway.commons.io.JsonUtils;
 import org.apache.causeway.viewer.restfulobjects.applib.util.JsonNodeUtils;
 import org.apache.causeway.viewer.restfulobjects.applib.util.PathNode;
 import org.apache.causeway.viewer.restfulobjects.applib.util.UrlEncodingUtils;
@@ -88,37 +89,25 @@ public class JsonRepresentation {
 
     private static Map<Class<?>, Function<JsonNode, ?>> REPRESENTATION_INSTANTIATORS = _Maps.newHashMap();
     static {
-        REPRESENTATION_INSTANTIATORS.put(String.class, new Function<JsonNode, String>() {
-            @Override
-            public String apply(final JsonNode input) {
-                if (!input.isTextual()) {
-                    throw new IllegalStateException("found node that is not a string " + input.toString());
-                }
-                return input.textValue();
-            }
-        });
-        REPRESENTATION_INSTANTIATORS.put(JsonNode.class, new Function<JsonNode, JsonNode>() {
-            @Override
-            public JsonNode apply(final JsonNode input) {
-                return input;
+        REPRESENTATION_INSTANTIATORS.put(String.class, input -> {
+            if (!input.isTextual()) {
+                throw new IllegalStateException("found node that is not a string " + input.toString());
             }
+            return input.textValue();
         });
+        REPRESENTATION_INSTANTIATORS.put(JsonNode.class, input -> input);
     }
 
     private static <T> Function<JsonNode, ?> representationInstantiatorFor(final Class<T> representationType) {
         Function<JsonNode, ?> transformer = REPRESENTATION_INSTANTIATORS.get(representationType);
         if (transformer == null) {
-            transformer = new Function<JsonNode, T>() {
-                @Override
-                public T apply(final JsonNode input) {
-                    try {
-                        final Constructor<T> constructor = representationType.getConstructor(JsonNode.class);
-                        return constructor.newInstance(input);
-                    } catch (final Exception e) {
-                        throw new IllegalArgumentException("Conversions from JsonNode to " + representationType + " are not supported");
-                    }
+            transformer = input -> {
+                try {
+                    final Constructor<T> constructor = representationType.getConstructor(JsonNode.class);
+                    return constructor.newInstance(input);
+                } catch (final Exception e) {
+                    throw new IllegalArgumentException("Conversions from JsonNode to " + representationType + " are not supported");
                 }
-
             };
             REPRESENTATION_INSTANTIATORS.put(representationType, transformer);
         }
@@ -129,7 +118,9 @@ public class JsonRepresentation {
         val repr = JsonRepresentation.newMap();
         if(_Strings.isNotEmpty(keyValuePairsAsJson)) {
             final Map<Object, Object> keyValuePairs = _Casts.uncheckedCast(
-                    _Json.readJson(Map.class, keyValuePairsAsJson).getValue().orElseThrow());
+                    JsonUtils.tryRead(Map.class, DataSource.ofStringUtf8(keyValuePairsAsJson))
+                        .ifFailureFail()
+                        .getValue().orElseThrow());
 
             keyValuePairs.forEach((key, value)->{
                 repr.mapPutString(""+key, ""+value);
@@ -1635,28 +1626,22 @@ public class JsonRepresentation {
         }
     }
 
-    private static final Function<Entry<String, JsonNode>, Entry<String, JsonRepresentation>> MAP_ENTRY_JSON_NODE_TO_JSON_REPRESENTATION = new Function<Entry<String, JsonNode>, Entry<String, JsonRepresentation>>() {
+    private static final Function<Entry<String, JsonNode>, Entry<String, JsonRepresentation>> MAP_ENTRY_JSON_NODE_TO_JSON_REPRESENTATION = input -> new Map.Entry<String, JsonRepresentation>() {
 
         @Override
-        public Entry<String, JsonRepresentation> apply(final Entry<String, JsonNode> input) {
-            return new Map.Entry<String, JsonRepresentation>() {
-
-                @Override
-                public String getKey() {
-                    return input.getKey();
-                }
+        public String getKey() {
+            return input.getKey();
+        }
 
-                @Override
-                public JsonRepresentation getValue() {
-                    return new JsonRepresentation(input.getValue());
-                }
+        @Override
+        public JsonRepresentation getValue() {
+            return new JsonRepresentation(input.getValue());
+        }
 
-                @Override
-                public JsonRepresentation setValue(final JsonRepresentation value) {
-                    final JsonNode setValue = input.setValue(value.asJsonNode());
-                    return new JsonRepresentation(setValue);
-                }
-            };
+        @Override
+        public JsonRepresentation setValue(final JsonRepresentation value) {
+            final JsonNode setValue = input.setValue(value.asJsonNode());
+            return new JsonRepresentation(setValue);
         }
     };
 
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/serialization/SerializationStrategy.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/serialization/SerializationStrategy.java
index 6ac64fe0e0..d1432c3c67 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/serialization/SerializationStrategy.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/resources/serialization/SerializationStrategy.java
@@ -22,7 +22,7 @@ import java.util.Collection;
 
 import javax.ws.rs.core.MediaType;
 
-import org.apache.causeway.commons.internal.resources._Json;
+import org.apache.causeway.commons.io.JsonUtils;
 import org.apache.causeway.viewer.restfulobjects.applib.RepresentationType;
 
 public enum SerializationStrategy {
@@ -36,19 +36,19 @@ public enum SerializationStrategy {
 
     JSON {
         @Override public Object entity(final Object jaxbAnnotatedObject) {
-            return _Json.toString(
+            return JsonUtils.toStringUtf8(
                     jaxbAnnotatedObject,
-                    _Json::jaxbAnnotationSupport);
+                    JsonUtils::jaxbAnnotationSupport);
         }
 
     },
 
     JSON_INDENTED {
         @Override public Object entity(final Object jaxbAnnotatedObject) {
-            return _Json.toString(
+            return JsonUtils.toStringUtf8(
                     jaxbAnnotatedObject,
-                    _Json::jaxbAnnotationSupport,
-                    _Json::indentedOutput);
+                    JsonUtils::jaxbAnnotationSupport,
+                    JsonUtils::indentedOutput);
         }
 
     },