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/02/02 16:30:15 UTC

[isis] 02/02: ISIS-2877: use ValueComposer on all remaining types

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

commit 19a1c46a5f25639c0947b459ecceb75197bff458
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed Feb 2 17:29:57 2022 +0100

    ISIS-2877: use ValueComposer on all remaining types
---
 .../isis/applib/util/schema/CommonDtoUtils.java    | 92 +++++++++++++++++++++-
 .../isis/applib/value/semantics/ValueComposer.java | 18 +++++
 .../value/semantics/ValueSemanticsAbstract.java    | 10 +--
 .../value/semantics/ValueSemanticsProvider.java    |  5 --
 .../isis/commons/internal/base/_NullSafe.java      |  9 +++
 .../encoder/EncodableFacetFromValueFacet.java      | 32 +++++---
 .../metamodel/facets/object/value/ValueFacet.java  |  6 --
 .../facets/object/value/ValueFacetAbstract.java    | 13 ---
 .../value/vsp/ValueSemanticsFacetAbstract.java     |  6 --
 .../identify/ObjectBookmarker_builtinHandlers.java | 11 +--
 .../schema/SchemaValueMarshallerAbstract.java      |  4 +-
 .../BufferedImageValueSemantics.java               | 64 +++++++++++----
 .../valuesemantics/CharacterValueSemantics.java    | 25 ++++--
 .../valuesemantics/EnumValueSemanticsAbstract.java | 29 ++++---
 .../valuesemantics/PasswordValueSemantics.java     | 22 +++---
 .../value/CharacterValueSemanticsProviderTest.java | 15 ++--
 .../value/PasswordValueSemanticsProviderTest.java  | 16 +++-
 .../ValueSemanticsProviderAbstractTestCase.java    |  6 +-
 .../jdo/ComplexNumberJdoValueSemantics.java        | 74 ++++++++++-------
 .../jpa/ComplexNumberJpaValueSemantics.java        | 74 ++++++++++-------
 .../testdomain/value/ValueSemanticsTester.java     | 16 +---
 .../rendering/domainobjects/JsonValueEncoder.java  | 23 ++++--
 22 files changed, 370 insertions(+), 200 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/util/schema/CommonDtoUtils.java b/api/applib/src/main/java/org/apache/isis/applib/util/schema/CommonDtoUtils.java
index 17c56b9..89aa9e8 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/util/schema/CommonDtoUtils.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/util/schema/CommonDtoUtils.java
@@ -40,9 +40,11 @@ import org.apache.isis.applib.value.Clob;
 import org.apache.isis.applib.value.semantics.ValueComposer.ValueDecomposition;
 import org.apache.isis.commons.internal.assertions._Assert;
 import org.apache.isis.commons.internal.base._Casts;
+import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.context._Context;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
+import org.apache.isis.commons.internal.resources._Json;
 import org.apache.isis.schema.cmd.v2.MapDto;
 import org.apache.isis.schema.cmd.v2.ParamDto;
 import org.apache.isis.schema.common.v2.BlobDto;
@@ -66,10 +68,96 @@ import lombok.experimental.UtilityClass;
 @UtilityClass
 public final class CommonDtoUtils {
 
+    // -- VALUE EXTRACT
+
+    public String getFundamentalValueAsJson(
+            final @Nullable ValueWithTypeDto valueDto) {
+        if(valueDto==null) {
+            return null;
+        }
+        return getFundamentalValueAsJson(valueDto.getType(),valueDto);
+    }
+
+    @SneakyThrows
+    public String getFundamentalValueAsJson(
+            final @NonNull ValueType valueType,
+            final @Nullable ValueDto valueDto) {
+
+        _NullSafe.toString(7);
+
+        if(valueDto==null) {
+            return null;
+        }
+        switch(valueType) {
+        case REFERENCE:
+        case COMPOSITE:
+        case COLLECTION:
+            throw _Exceptions.unsupportedOperation("valueType %s is not fundamental", valueType);
+
+        case STRING:
+            return valueDto.getString();
+        case BYTE:
+            return _NullSafe.toString(valueDto.getByte());
+        case SHORT:
+            return _NullSafe.toString(valueDto.getShort());
+        case INT:
+            return _NullSafe.toString(valueDto.getInt());
+        case LONG:
+            return _NullSafe.toString(valueDto.getLong());
+        case FLOAT:
+            return _NullSafe.toString(valueDto.getFloat());
+        case DOUBLE:
+            return _NullSafe.toString(valueDto.getDouble());
+        case BOOLEAN:
+            return _NullSafe.toString(valueDto.isBoolean());
+        case CHAR:
+            final String aChar = valueDto.getChar();
+            if(_Strings.isNullOrEmpty(aChar)) { return null; }
+            return ""+aChar.charAt(0);
+        case BIG_DECIMAL:
+            return _NullSafe.toString(valueDto.getBigDecimal());
+        case BIG_INTEGER:
+            return _NullSafe.toString(valueDto.getBigInteger());
+        case LOCAL_DATE:
+            return _NullSafe.toString(valueDto.getLocalDate());
+        case LOCAL_TIME:
+            return _NullSafe.toString(valueDto.getLocalTime());
+        case LOCAL_DATE_TIME:
+            return _NullSafe.toString(valueDto.getLocalDateTime());
+        case OFFSET_DATE_TIME:
+            return _NullSafe.toString(valueDto.getOffsetDateTime());
+        case OFFSET_TIME:
+            return _NullSafe.toString(valueDto.getOffsetTime());
+        case ZONED_DATE_TIME:
+            return _NullSafe.toString(valueDto.getZonedDateTime());
+        case ENUM:
+            final EnumDto enumDto = valueDto.getEnum();
+            final String enumType = enumDto.getEnumType();
+            @SuppressWarnings("rawtypes")
+            final Class<? extends Enum> enumClass =
+                    _Casts.uncheckedCast(_Context.loadClassAndInitialize(enumType));
+            return Enum.valueOf(_Casts.uncheckedCast(enumClass), enumDto.getEnumName()).name();
+        case BLOB:
+            final BlobDto blobDto = valueDto.getBlob();
+            return blobDto!=null
+                    ? _Json.toString(blobDto).presentElseFail()
+                    : null;
+        case CLOB:
+            final ClobDto clobDto = valueDto.getClob();
+            return clobDto!=null
+                    ? _Json.toString(clobDto).presentElseFail()
+                    : null;
+        case VOID:
+            return null;
+        default:
+            throw _Exceptions.unmatchedCase(valueType);
+        }
+    }
+
     // -- VALUE RECORD
 
-    public <D extends ValueDto, T> D recordFundamentalValue(
-            final @NonNull  ValueType valueType,
+    public <D extends ValueDto> D recordFundamentalValue(
+            final @NonNull ValueType valueType,
             final D valueDto,
             final Object pojo) {
 
diff --git a/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueComposer.java b/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueComposer.java
index 21f7665..4fc42b9 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueComposer.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueComposer.java
@@ -18,7 +18,9 @@
  */
 package org.apache.isis.applib.value.semantics;
 
+import org.apache.isis.applib.util.schema.CommonDtoUtils;
 import org.apache.isis.commons.internal.base._Either;
+import org.apache.isis.commons.internal.resources._Json;
 import org.apache.isis.schema.common.v2.TypedTupleDto;
 import org.apache.isis.schema.common.v2.ValueWithTypeDto;
 
@@ -51,6 +53,22 @@ public interface ValueComposer<T> {
         private ValueDecomposition(final ValueWithTypeDto left, final TypedTupleDto right) {
             super(left, right);
         }
+
+        // used by RO-Viewer to render values
+        public String toJson() {
+            return this.<String>fold(
+                    fundamental->CommonDtoUtils.getFundamentalValueAsJson(fundamental),
+                    composite->composite!=null
+                            ? _Json.toString(composite).presentElseFail()
+                            : null);
+        }
+
+        // used by EncodableFacet
+        public static ValueDecomposition fromJson(final String encodedData) {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
     }
 
     /**
diff --git a/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsAbstract.java b/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsAbstract.java
index c85c136..00b996f 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsAbstract.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsAbstract.java
@@ -86,11 +86,11 @@ implements
         return this instanceof Renderer ? (Renderer<T>)this : null;
     }
 
-    @SuppressWarnings("unchecked")
-    @Override
-    public EncoderDecoder<T> getEncoderDecoder() {
-        return this instanceof EncoderDecoder ? (EncoderDecoder<T>)this : null;
-    }
+//    @SuppressWarnings("unchecked")
+//    @Override
+//    public EncoderDecoder<T> getEncoderDecoder() {
+//        return this instanceof EncoderDecoder ? (EncoderDecoder<T>)this : null;
+//    }
 
     @SuppressWarnings("unchecked")
     @Override
diff --git a/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsProvider.java b/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsProvider.java
index 54b4a37..fb2f28b 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsProvider.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/value/semantics/ValueSemanticsProvider.java
@@ -91,11 +91,6 @@ public interface ValueSemanticsProvider<T> {
     Parser<T> getParser();
 
     /**
-     * The {@link EncoderDecoder}, if any.
-     */
-    EncoderDecoder<T> getEncoderDecoder();
-
-    /**
      * The {@link DefaultsProvider}, if any.
      *
      * <p>
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java b/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java
index 72d8ff2..469c14c 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java
@@ -302,6 +302,13 @@ public final class _NullSafe {
     public static int size(final @Nullable short[] array){ return array!=null ? array.length : 0; }
     public static <T> int size(final @Nullable T[] array){ return array!=null ? array.length : 0; }
 
+    // -- TO STRING
+
+    public static String toString(final @Nullable Object obj) {
+        return obj!=null
+                ? obj.toString()
+                : null;
+    }
 
     // -- MAP
 
@@ -336,4 +343,6 @@ public final class _NullSafe {
                 : map.entrySet();
     }
 
+
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/encodeable/encoder/EncodableFacetFromValueFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/encodeable/encoder/EncodableFacetFromValueFacet.java
index 1f8cd1b..2677a97 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/encodeable/encoder/EncodableFacetFromValueFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/encodeable/encoder/EncodableFacetFromValueFacet.java
@@ -21,7 +21,8 @@ package org.apache.isis.core.metamodel.facets.object.encodeable.encoder;
 import java.util.Optional;
 import java.util.function.BiConsumer;
 
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
+import org.apache.isis.applib.value.semantics.ValueComposer;
+import org.apache.isis.applib.value.semantics.ValueComposer.ValueDecomposition;
 import org.apache.isis.commons.internal.assertions._Assert;
 import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
@@ -29,6 +30,8 @@ import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
 import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 
+import lombok.val;
+
 public class EncodableFacetFromValueFacet
 extends FacetAbstract
 implements EncodableFacet {
@@ -37,24 +40,25 @@ implements EncodableFacet {
     public static final String ENCODED_NULL = "NULL";
 
     public static Optional<EncodableFacet> create(final ValueFacet<?> valueFacet, final FacetHolder holder) {
-        return valueFacet.selectDefaultEncoderDecoder()
-                .map(encoderDecoder->new EncodableFacetFromValueFacet(encoderDecoder, holder));
+        return valueFacet.selectDefaultComposer()
+                .map(composer->new EncodableFacetFromValueFacet(composer, holder));
     }
 
     /**
      * JUnit support.
      */
-    public static EncodableFacetFromValueFacet forTesting(final EncoderDecoder<?> encoderDecoder, final FacetHolder holder) {
-        return new EncodableFacetFromValueFacet(encoderDecoder, holder);
+    public static EncodableFacetFromValueFacet forTesting(
+            final ValueComposer<?> composer, final FacetHolder holder) {
+        return new EncodableFacetFromValueFacet(composer, holder);
     }
 
     // -- CONSTRUCTION
 
-    private final EncoderDecoder<?> encoderDecoder;
+    private final ValueComposer<?> composer;
 
-    private EncodableFacetFromValueFacet(final EncoderDecoder<?> encoderDecoder, final FacetHolder holder) {
+    private EncodableFacetFromValueFacet(final ValueComposer<?> encoderDecoder, final FacetHolder holder) {
         super(EncodableFacet.class, holder);
-        this.encoderDecoder = encoderDecoder;
+        this.composer = encoderDecoder;
     }
 
     @Override
@@ -63,7 +67,7 @@ implements EncodableFacet {
         if (ENCODED_NULL.equals(encodedData)) {
             return null;
         } else {
-            final Object decodedObject = encoderDecoder.fromEncodedString(encodedData);
+            final Object decodedObject = composer.compose(ValueDecomposition.fromJson(encodedData));
             return getObjectManager().adapt(decodedObject);
         }
     }
@@ -72,21 +76,23 @@ implements EncodableFacet {
     public String toEncodedString(final ManagedObject adapter) {
         return adapter == null
                 ? ENCODED_NULL
-                : encode(encoderDecoder, adapter.getPojo());
+                : encode(composer, adapter.getPojo());
     }
 
     @Override
     public void visitAttributes(final BiConsumer<String, Object> visitor) {
         super.visitAttributes(visitor);
-        visitor.accept("codec", encoderDecoder.toString());
+        visitor.accept("composer", composer.toString());
     }
 
     // -- HELPER
 
-    private static <T> String encode(final EncoderDecoder<T> encoderDecoder, final Object pojo) {
+    private static <T> String encode(final ValueComposer<T> composer, final Object pojo) {
         @SuppressWarnings("unchecked")
         T pojoAsT = (T) pojo;
-        return encoderDecoder.toEncodedString(pojoAsT);
+        val valueAsJson = composer.decompose(pojoAsT)
+                .toJson();
+        return valueAsJson;
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacet.java
index 2ca7786..976aa0d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacet.java
@@ -26,7 +26,6 @@ import org.springframework.lang.Nullable;
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.id.LogicalType;
 import org.apache.isis.applib.value.semantics.DefaultsProvider;
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
 import org.apache.isis.applib.value.semantics.OrderRelation;
 import org.apache.isis.applib.value.semantics.Parser;
 import org.apache.isis.applib.value.semantics.Renderer;
@@ -74,11 +73,6 @@ public interface ValueFacet<T> extends Facet {
     /** no qualifiers allowed on the default semantics provider*/
     Optional<DefaultsProvider<T>> selectDefaultDefaultsProvider();
 
-    // -- ENCODER DECODER
-
-    /** no qualifiers allowed on the default semantics provider*/
-    Optional<EncoderDecoder<T>> selectDefaultEncoderDecoder();
-
     // -- COMPOSER
 
     /** no qualifiers allowed on the default semantics provider*/
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java
index 6a9f1b7..e5a9497 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/ValueFacetAbstract.java
@@ -28,7 +28,6 @@ import org.springframework.lang.Nullable;
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.id.LogicalType;
 import org.apache.isis.applib.value.semantics.DefaultsProvider;
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
 import org.apache.isis.applib.value.semantics.OrderRelation;
 import org.apache.isis.applib.value.semantics.Parser;
 import org.apache.isis.applib.value.semantics.Renderer;
@@ -134,18 +133,6 @@ implements ValueFacet<T> {
                 .findFirst();
     }
 
-    // -- ENCODER DECODER
-
-    @Override
-    public Optional<EncoderDecoder<T>> selectDefaultEncoderDecoder() {
-        return getValueSemantics()
-                .stream()
-                .filter(isMatchingAnyOf(Can.empty()))
-                .map(ValueSemanticsProvider::getEncoderDecoder)
-                .filter(_NullSafe::isPresent)
-                .findFirst();
-    }
-
     // -- COMPOSER
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/vsp/ValueSemanticsFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/vsp/ValueSemanticsFacetAbstract.java
index d7e8158..12ff50c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/vsp/ValueSemanticsFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/value/vsp/ValueSemanticsFacetAbstract.java
@@ -19,7 +19,6 @@
 package org.apache.isis.core.metamodel.facets.object.value.vsp;
 
 import org.apache.isis.applib.value.semantics.DefaultsProvider;
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
 import org.apache.isis.applib.value.semantics.Parser;
 import org.apache.isis.applib.value.semantics.ValueSemanticsProvider;
 import org.apache.isis.core.metamodel.facetapi.Facet;
@@ -59,11 +58,6 @@ implements ValueSemanticsProvider<T> {
     }
 
     @Override
-    public final EncoderDecoder<T> getEncoderDecoder() {
-        return valueSemantics.getEncoderDecoder();
-    }
-
-    @Override
     public final DefaultsProvider<T> getDefaultsProvider() {
         return valueSemantics.getDefaultsProvider();
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java
index 4fddfc3..1c7276f 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/objectmanager/identify/ObjectBookmarker_builtinHandlers.java
@@ -25,7 +25,7 @@ import java.util.UUID;
 
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.bookmark.Oid;
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
+import org.apache.isis.applib.value.semantics.ValueComposer;
 import org.apache.isis.commons.internal.base._Bytes;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.debug._Debug;
@@ -151,16 +151,17 @@ class ObjectBookmarker_builtinHandlers {
             }
 
             val valueFacet = spec.getFacet(ValueFacet.class);
-            EncoderDecoder<Object> codec = (EncoderDecoder) valueFacet.selectDefaultEncoderDecoder()
+            ValueComposer<Object> composer = (ValueComposer) valueFacet.selectDefaultComposer()
                     .orElseThrow(()->_Exceptions.illegalArgument(
                             "Cannot create a bookmark for the value type %s, "
-                          + "as no appropriate EncoderDecoder could be found.",
+                          + "as no appropriate ValueComposer could be found.",
                           managedObject.getSpecification().getCorrespondingClass().getName()));
 
-            val encoded = codec.toEncodedString(managedObject.getPojo()).getBytes();
+            val valueAsJson = composer.decompose(managedObject.getPojo())
+                    .toJson();
 
             val identifier = _Strings.ofBytes(
-                    _Bytes.asUrlBase64.apply(encoded),
+                    _Bytes.asUrlBase64.apply(valueAsJson.getBytes()),
                     StandardCharsets.UTF_8);
 
             return Bookmark.forLogicalTypeAndIdentifier(spec.getLogicalType(), identifier);
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/schema/SchemaValueMarshallerAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/schema/SchemaValueMarshallerAbstract.java
index f1c9343..a0ab3ae 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/schema/SchemaValueMarshallerAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/schema/SchemaValueMarshallerAbstract.java
@@ -81,9 +81,7 @@ implements SchemaValueMarshaller {
                     && !semantics.getCorrespondingClass().equals(String.class);
 
             return of(correspondingClass, feature, Optional.ofNullable(semantics),
-                    supportsConversionViaEncoderDecoder
-                        ? Optional.ofNullable(semantics.getEncoderDecoder())
-                        : Optional.empty(),
+                    Optional.empty(),
                     semantics!=null
                         ? Optional.ofNullable(semantics.getConverter())
                         : Optional.empty(),
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/BufferedImageValueSemantics.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/BufferedImageValueSemantics.java
index 840ca01..b79ee92 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/BufferedImageValueSemantics.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/BufferedImageValueSemantics.java
@@ -26,15 +26,18 @@ import javax.inject.Named;
 import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Component;
 
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
+import org.apache.isis.applib.value.Blob;
+import org.apache.isis.applib.value.NamedWithMimeType.CommonMimeType;
 import org.apache.isis.applib.value.semantics.OrderRelation;
+import org.apache.isis.applib.value.semantics.ValueComposer;
 import org.apache.isis.applib.value.semantics.ValueSemanticsAbstract;
 import org.apache.isis.commons.collections.Can;
-import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.image._Images;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ManagedObjects;
+import org.apache.isis.schema.common.v2.BlobDto;
 import org.apache.isis.schema.common.v2.ValueType;
+import org.apache.isis.schema.common.v2.ValueWithTypeDto;
 
 @Component
 @Named("isis.val.BufferedImageValueSemantics")
@@ -43,7 +46,7 @@ extends ValueSemanticsAbstract<BufferedImage>
 implements
     ImageValueSemantics,
     OrderRelation<BufferedImage, Void>,
-    EncoderDecoder<BufferedImage> {
+    ValueComposer<BufferedImage> {
 
     @Override
     public Class<BufferedImage> getCorrespondingClass() {
@@ -52,7 +55,7 @@ implements
 
     @Override
     public ValueType getSchemaValueType() {
-        return UNREPRESENTED;
+        return ValueType.BLOB;
     }
 
     // -- ORDER RELATION
@@ -72,26 +75,53 @@ implements
         return compare(a, b, epsilon) == 0;
     }
 
-    // -- ENCODER DECODER
+    // -- COMPOSER
 
     @Override
-    public String toEncodedString(final @Nullable BufferedImage image) {
-        if(image==null) {
-            return null;
-        }
-        return _Images.toBase64(image);
+    public ValueDecomposition decompose(final BufferedImage value) {
+        return decomposeAsNullable(value, this::toBlob, ()->null);
     }
 
     @Override
-    public BufferedImage fromEncodedString(final @Nullable String base64ImageData) {
-        if(_Strings.isNullOrEmpty(base64ImageData)) {
-            return null;
-        }
-        /*sonar-ignore-on*/
-        return _Images.fromBase64(base64ImageData);
-        /*sonar-ignore-off*/
+    public BufferedImage compose(final ValueDecomposition decomposition) {
+        return composeFromNullable(
+                decomposition, ValueWithTypeDto::getBlob, this::fromBlobDto, ()->null);
+    }
+
+    private BufferedImage fromBlobDto(final BlobDto blobDto) {
+        return blobDto!=null
+                    && blobDto.getBytes()!=null
+                            ? _Images.fromBytes(blobDto.getBytes())
+                            : null;
+    }
+
+    private Blob toBlob(final BufferedImage img) {
+        return img!=null
+                // arbitrary name and mimetype - not used when recovering the image
+                ? new Blob("image", CommonMimeType.PNG.getBaseType(), _Images.toBytes(img))
+                : null;
     }
 
+    // -- ENCODER DECODER
+
+//    @Override
+//    public String toEncodedString(final @Nullable BufferedImage image) {
+//        if(image==null) {
+//            return null;
+//        }
+//        return _Images.toBase64(image);
+//    }
+//
+//    @Override
+//    public BufferedImage fromEncodedString(final @Nullable String base64ImageData) {
+//        if(_Strings.isNullOrEmpty(base64ImageData)) {
+//            return null;
+//        }
+//        /*sonar-ignore-on*/
+//        return _Images.fromBase64(base64ImageData);
+//        /*sonar-ignore-off*/
+//    }
+
     // -- FACET
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/CharacterValueSemantics.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/CharacterValueSemantics.java
index 3b00f28..56765a8 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/CharacterValueSemantics.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/CharacterValueSemantics.java
@@ -18,18 +18,21 @@
  */
 package org.apache.isis.core.metamodel.valuesemantics;
 
+import java.util.function.UnaryOperator;
+
 import javax.inject.Named;
 
 import org.springframework.stereotype.Component;
 
 import org.apache.isis.applib.exceptions.recoverable.InvalidEntryException;
 import org.apache.isis.applib.value.semantics.DefaultsProvider;
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
 import org.apache.isis.applib.value.semantics.Parser;
 import org.apache.isis.applib.value.semantics.Renderer;
+import org.apache.isis.applib.value.semantics.ValueComposer;
 import org.apache.isis.applib.value.semantics.ValueSemanticsAbstract;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.base._Strings;
+import org.apache.isis.schema.common.v2.ValueDto;
 import org.apache.isis.schema.common.v2.ValueType;
 
 import lombok.val;
@@ -43,7 +46,7 @@ public class CharacterValueSemantics
 extends ValueSemanticsAbstract<Character>
 implements
     DefaultsProvider<Character>,
-    EncoderDecoder<Character>,
+    ValueComposer<Character>,
     Parser<Character>,
     Renderer<Character> {
 
@@ -62,16 +65,24 @@ implements
         return (char) 0;
     }
 
-    // -- ENCODER DECODER
+    // -- COMPOSER
 
     @Override
-    public String toEncodedString(final Character object) {
-        return object.toString();
+    public ValueDecomposition decompose(final Character value) {
+        return decomposeAsNullable(value, UnaryOperator.identity(), ()->null);
     }
 
     @Override
-    public Character fromEncodedString(final String data) {
-        return Character.valueOf(data.charAt(0));
+    public Character compose(final ValueDecomposition decomposition) {
+        return composeFromNullable(
+                decomposition, ValueDto::getChar, this::fromString, ()->null);
+    }
+
+    private Character fromString(final String data) {
+        return data!=null
+                && data.length()>0
+                ? Character.valueOf(data.charAt(0))
+                : null;
     }
 
     // -- RENDERER
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/EnumValueSemanticsAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/EnumValueSemanticsAbstract.java
index 95fb8c9..f626154 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/EnumValueSemanticsAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/EnumValueSemanticsAbstract.java
@@ -19,6 +19,7 @@
 package org.apache.isis.core.metamodel.valuesemantics;
 
 import java.lang.reflect.Method;
+import java.util.function.UnaryOperator;
 import java.util.stream.Stream;
 
 import org.apache.isis.applib.annotation.Introspection.IntrospectionPolicy;
@@ -28,9 +29,9 @@ import org.apache.isis.applib.services.i18n.TranslationContext;
 import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.applib.util.Enums;
 import org.apache.isis.applib.value.semantics.DefaultsProvider;
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
 import org.apache.isis.applib.value.semantics.Parser;
 import org.apache.isis.applib.value.semantics.Renderer;
+import org.apache.isis.applib.value.semantics.ValueComposer;
 import org.apache.isis.applib.value.semantics.ValueSemanticsAbstract;
 import org.apache.isis.applib.value.semantics.ValueSemanticsProvider;
 import org.apache.isis.commons.collections.Can;
@@ -39,7 +40,9 @@ import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.config.progmodel.ProgrammingModelConstants.ObjectSupportMethod;
 import org.apache.isis.core.metamodel.commons.MethodExtensions;
 import org.apache.isis.core.metamodel.methods.MethodFinder;
+import org.apache.isis.schema.common.v2.EnumDto;
 import org.apache.isis.schema.common.v2.ValueType;
+import org.apache.isis.schema.common.v2.ValueWithTypeDto;
 
 import lombok.Getter;
 import lombok.val;
@@ -49,7 +52,7 @@ public class EnumValueSemanticsAbstract<T extends Enum<T>>
 extends ValueSemanticsAbstract<T>
 implements
     DefaultsProvider<T>,
-    EncoderDecoder<T>,
+    ValueComposer<T>,
     Parser<T>,
     Renderer<T> {
 
@@ -108,23 +111,31 @@ implements
         return correspondingClass.getEnumConstants()[0];
     }
 
-    // -- ENCODER/DECODER
+    // -- COMPOSER
 
     @Override
-    public String toEncodedString(final T object) {
-        return object!=null ? object.name() : null;
+    public ValueDecomposition decompose(final T value) {
+        return decomposeAsNullable(value, UnaryOperator.identity(), ()->null);
     }
 
     @Override
-    public T fromEncodedString(final String data) {
-        return data!=null
+    public T compose(final ValueDecomposition decomposition) {
+        return composeFromNullable(
+                decomposition, ValueWithTypeDto::getEnum, this::fromEnumName, ()->null);
+    }
+
+    private T fromEnumName(final EnumDto enumDto) {
+
+        val enumName = enumDto.getEnumName();
+
+        return enumName!=null
                 ? Stream.of(correspondingClass.getEnumConstants())
-                        .filter(e->e.name().equals(data))
+                        .filter(e->e.name().equals(enumName))
                         .findFirst()
                         .orElseThrow(()->_Exceptions
                                 .noSuchElement("enum %s has no matching %s",
                                         correspondingClass.getName(),
-                                        data))
+                                        enumName))
                 : null;
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/PasswordValueSemantics.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/PasswordValueSemantics.java
index 9202ad2..4a1f7e4 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/PasswordValueSemantics.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/valuesemantics/PasswordValueSemantics.java
@@ -23,20 +23,21 @@ import javax.inject.Named;
 import org.springframework.stereotype.Component;
 
 import org.apache.isis.applib.value.Password;
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
 import org.apache.isis.applib.value.semantics.Parser;
 import org.apache.isis.applib.value.semantics.Renderer;
+import org.apache.isis.applib.value.semantics.ValueComposer;
 import org.apache.isis.applib.value.semantics.ValueSemanticsAbstract;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.schema.common.v2.ValueType;
+import org.apache.isis.schema.common.v2.ValueWithTypeDto;
 
 @Component
 @Named("isis.val.PasswordValueSemantics")
 public class PasswordValueSemantics
 extends ValueSemanticsAbstract<Password>
 implements
-    EncoderDecoder<Password>,
+    ValueComposer<Password>,
     Parser<Password>,
     Renderer<Password> {
 
@@ -50,22 +51,17 @@ implements
         return ValueType.STRING;
     }
 
-    // -- ENCODER DECODER
+    // -- COMPOSER
 
     @Override
-    public String toEncodedString(final Password value) {
-        if(value==null) {
-            return null;
-        }
-        return value.getPassword();
+    public ValueDecomposition decompose(final Password value) {
+        return decomposeAsNullable(value, Password::getPassword, ()->null);
     }
 
     @Override
-    public Password fromEncodedString(final String data) {
-        if(data==null) {
-            return null;
-        }
-        return new Password(data);
+    public Password compose(final ValueDecomposition decomposition) {
+        return composeFromNullable(
+                decomposition, ValueWithTypeDto::getString, Password::new, ()->null);
     }
 
     // -- RENDERER
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/CharacterValueSemanticsProviderTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/CharacterValueSemanticsProviderTest.java
index f5dde9c..44e7068 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/CharacterValueSemanticsProviderTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/CharacterValueSemanticsProviderTest.java
@@ -25,25 +25,26 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import org.apache.isis.applib.exceptions.recoverable.InvalidEntryException;
+import org.apache.isis.applib.value.semantics.ValueComposer.ValueDecomposition;
 import org.apache.isis.core.metamodel.valuesemantics.CharacterValueSemantics;
 
 public class CharacterValueSemanticsProviderTest
 extends ValueSemanticsProviderAbstractTestCase {
 
-    private CharacterValueSemantics value;
+    private CharacterValueSemantics valueSemantics;
 
     private Character character;
 
     @Before
     public void setUpObjects() throws Exception {
         character = Character.valueOf('r');
-        setSemantics(value = new CharacterValueSemantics());
+        setSemantics(valueSemantics = new CharacterValueSemantics());
     }
 
     @Test
     public void testParseLongString() throws Exception {
         try {
-            value.parseTextRepresentation(null, "one");
+            valueSemantics.parseTextRepresentation(null, "one");
             fail();
         } catch (final InvalidEntryException expected) {
         }
@@ -51,23 +52,23 @@ extends ValueSemanticsProviderAbstractTestCase {
 
     @Test
     public void testTitleOf() {
-        assertEquals("r", value.simpleTextPresentation(null, character));
+        assertEquals("r", valueSemantics.simpleTextPresentation(null, character));
     }
 
     @Test
     public void testValidParse() throws Exception {
-        final Object parse = value.parseTextRepresentation(null, "t");
+        final Object parse = valueSemantics.parseTextRepresentation(null, "t");
         assertEquals(Character.valueOf('t'), parse);
     }
 
     @Test
     public void testEncode() throws Exception {
-        assertEquals("r", value.toEncodedString(character));
+        assertEquals("r", valueSemantics.decompose(character).toJson());
     }
 
     @Test
     public void testDecode() throws Exception {
-        final Object restore = value.fromEncodedString("Y");
+        final Object restore = valueSemantics.compose(ValueDecomposition.fromJson("Y"));
         assertEquals(Character.valueOf('Y'), restore);
     }
 }
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/PasswordValueSemanticsProviderTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/PasswordValueSemanticsProviderTest.java
index 4f94cd0..837fccf 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/PasswordValueSemanticsProviderTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/PasswordValueSemanticsProviderTest.java
@@ -24,22 +24,30 @@ import org.junit.Test;
 import static org.junit.Assert.assertEquals;
 
 import org.apache.isis.applib.value.Password;
+import org.apache.isis.applib.value.semantics.ValueComposer.ValueDecomposition;
 import org.apache.isis.core.metamodel.valuesemantics.PasswordValueSemantics;
 
 public class PasswordValueSemanticsProviderTest
 extends ValueSemanticsProviderAbstractTestCase {
 
-    private PasswordValueSemantics adapter;
+    private PasswordValueSemantics valueSemantics;
     private Password password;
 
     @Before
     public void setUpObjects() throws Exception {
-        setSemantics(adapter = new PasswordValueSemantics());
+        setSemantics(valueSemantics = new PasswordValueSemantics());
         password = new Password("secret");
     }
 
     @Test
-    public void testEncoding() {
-        assertEquals("secret", adapter.toEncodedString(password));
+    public void testEncode() throws Exception {
+        assertEquals("secret", valueSemantics.decompose(password).toJson());
     }
+
+    @Test
+    public void testDecode() throws Exception {
+        final Object restore = valueSemantics.compose(ValueDecomposition.fromJson("secret"));
+        assertEquals(password, restore);
+    }
+
 }
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java
index a25f2e4..c72d766 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/value/ValueSemanticsProviderAbstractTestCase.java
@@ -100,7 +100,7 @@ public abstract class ValueSemanticsProviderAbstractTestCase {
     protected void setSemantics(final ValueSemanticsAbstract<?> valueSemantics) {
         this.valueSemanticsProvider = valueSemantics;
         this.encodeableFacet = EncodableFacetFromValueFacet.forTesting(
-                valueSemantics.getEncoderDecoder(),
+                valueSemantics.getComposer(),
                 mockFacetHolder);
     }
 
@@ -135,7 +135,7 @@ public abstract class ValueSemanticsProviderAbstractTestCase {
 
     @Test
     public void testDecodeNULL() throws Exception {
-        Assume.assumeThat(valueSemanticsProvider.getEncoderDecoder(), is(not(nullValue())));
+        Assume.assumeThat(valueSemanticsProvider.getComposer(), is(not(nullValue())));
 
         final Object newValue = encodeableFacet.fromEncodedString(EncodableFacetFromValueFacet.ENCODED_NULL);
         assertNull(newValue);
@@ -143,7 +143,7 @@ public abstract class ValueSemanticsProviderAbstractTestCase {
 
     @Test
     public void testEmptyEncoding() {
-        Assume.assumeThat(valueSemanticsProvider.getEncoderDecoder(), is(not(nullValue())));
+        Assume.assumeThat(valueSemanticsProvider.getComposer(), is(not(nullValue())));
 
         assertEquals(EncodableFacetFromValueFacet.ENCODED_NULL, encodeableFacet.toEncodedString(null));
     }
diff --git a/examples/demo/domain/src/main/java/demoapp/dom/domain/objects/other/embedded/jdo/ComplexNumberJdoValueSemantics.java b/examples/demo/domain/src/main/java/demoapp/dom/domain/objects/other/embedded/jdo/ComplexNumberJdoValueSemantics.java
index 9526f2a..ca8fe16 100644
--- a/examples/demo/domain/src/main/java/demoapp/dom/domain/objects/other/embedded/jdo/ComplexNumberJdoValueSemantics.java
+++ b/examples/demo/domain/src/main/java/demoapp/dom/domain/objects/other/embedded/jdo/ComplexNumberJdoValueSemantics.java
@@ -18,22 +18,17 @@
  */
 package demoapp.dom.domain.objects.other.embedded.jdo;
 
-import java.util.stream.Collectors;
-
 import org.springframework.stereotype.Component;
 
+import org.apache.isis.applib.util.schema.CommonDtoUtils;
 import org.apache.isis.applib.value.semantics.DefaultsProvider;
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
 import org.apache.isis.applib.value.semantics.Parser;
 import org.apache.isis.applib.value.semantics.Renderer;
+import org.apache.isis.applib.value.semantics.ValueComposer;
 import org.apache.isis.applib.value.semantics.ValueSemanticsAbstract;
 import org.apache.isis.applib.value.semantics.ValueSemanticsProvider;
-import org.apache.isis.commons.internal.base._NullSafe;
-import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.schema.common.v2.ValueType;
 
-import lombok.val;
-
 // tag::class[]
 @Component
 public class ComplexNumberJdoValueSemantics
@@ -90,36 +85,55 @@ public class ComplexNumberJdoValueSemantics
 
 // tag::getEncoderDecoder[]
     @Override
-    public EncoderDecoder<ComplexNumberJdo> getEncoderDecoder() {
+    public ValueComposer<ComplexNumberJdo> getComposer() {
 // end::getEncoderDecoder[]
         // ...
 // tag::getEncoderDecoder[]
-        return new EncoderDecoder<ComplexNumberJdo>() {
+        return new ValueComposer<ComplexNumberJdo>() {
+
             @Override
-            public String toEncodedString(final ComplexNumberJdo cn) {
-                if(cn==null) {
-                    return null;
-                }
-                val re = Double.doubleToLongBits(cn.getRe());
-                val im = Double.doubleToLongBits(cn.getIm());
-                return String.format("%s:%s",
-                        Long.toHexString(re), Long.toHexString(im));
+            public ValueDecomposition decompose(final ComplexNumberJdo value) {
+                return CommonDtoUtils.typedTupleBuilder(value)
+                        .addFundamentalType(ValueType.DOUBLE, "re", ComplexNumberJdo::getRe)
+                        .addFundamentalType(ValueType.DOUBLE, "im", ComplexNumberJdo::getIm)
+                        .buildAsDecomposition();
             }
+
             @Override
-            public ComplexNumberJdo fromEncodedString(final String str) {
-                if(_NullSafe.isEmpty(str)) {
-                    return null;
-                }
-                val chunks = _Strings.splitThenStream(str, ":")
-                    .limit(2)
-                    .collect(Collectors.toList());
-                if(chunks.size()<2) {
-                    throw new IllegalArgumentException("Invalid format " + str);
-                }
-                val re = Double.longBitsToDouble(Long.parseLong(chunks.get(0), 16));
-                val im = Double.longBitsToDouble(Long.parseLong(chunks.get(1), 16));
-                return ComplexNumberJdo.of(re, im);
+            public ComplexNumberJdo compose(final ValueDecomposition decomposition) {
+                return decomposition.right()
+                        .map(CommonDtoUtils::typedTupleAsMap)
+                        .map(map->ComplexNumberJdo.of(
+                                (Double)map.get("re"),
+                                (Double)map.get("im")))
+                        .orElse(null);
             }
+
+//            @Override
+//            public String toEncodedString(final ComplexNumberJdo cn) {
+//                if(cn==null) {
+//                    return null;
+//                }
+//                val re = Double.doubleToLongBits(cn.getRe());
+//                val im = Double.doubleToLongBits(cn.getIm());
+//                return String.format("%s:%s",
+//                        Long.toHexString(re), Long.toHexString(im));
+//            }
+//            @Override
+//            public ComplexNumberJdo fromEncodedString(final String str) {
+//                if(_NullSafe.isEmpty(str)) {
+//                    return null;
+//                }
+//                val chunks = _Strings.splitThenStream(str, ":")
+//                    .limit(2)
+//                    .collect(Collectors.toList());
+//                if(chunks.size()<2) {
+//                    throw new IllegalArgumentException("Invalid format " + str);
+//                }
+//                val re = Double.longBitsToDouble(Long.parseLong(chunks.get(0), 16));
+//                val im = Double.longBitsToDouble(Long.parseLong(chunks.get(1), 16));
+//                return ComplexNumberJdo.of(re, im);
+//            }
         };
     }
 // end::getEncoderDecoder[]
diff --git a/examples/demo/domain/src/main/java/demoapp/dom/domain/objects/other/embedded/jpa/ComplexNumberJpaValueSemantics.java b/examples/demo/domain/src/main/java/demoapp/dom/domain/objects/other/embedded/jpa/ComplexNumberJpaValueSemantics.java
index 889a5fb..ce1190e 100644
--- a/examples/demo/domain/src/main/java/demoapp/dom/domain/objects/other/embedded/jpa/ComplexNumberJpaValueSemantics.java
+++ b/examples/demo/domain/src/main/java/demoapp/dom/domain/objects/other/embedded/jpa/ComplexNumberJpaValueSemantics.java
@@ -18,23 +18,18 @@
  */
 package demoapp.dom.domain.objects.other.embedded.jpa;
 
-import java.util.stream.Collectors;
-
 import org.springframework.context.annotation.Profile;
 import org.springframework.stereotype.Component;
 
+import org.apache.isis.applib.util.schema.CommonDtoUtils;
 import org.apache.isis.applib.value.semantics.DefaultsProvider;
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
 import org.apache.isis.applib.value.semantics.Parser;
 import org.apache.isis.applib.value.semantics.Renderer;
+import org.apache.isis.applib.value.semantics.ValueComposer;
 import org.apache.isis.applib.value.semantics.ValueSemanticsAbstract;
 import org.apache.isis.applib.value.semantics.ValueSemanticsProvider;
-import org.apache.isis.commons.internal.base._NullSafe;
-import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.schema.common.v2.ValueType;
 
-import lombok.val;
-
 @Profile("demo-jpa")
 // tag::class[]
 @Component
@@ -92,36 +87,55 @@ public class ComplexNumberJpaValueSemantics
 
 // tag::getEncoderDecoder[]
     @Override
-    public EncoderDecoder<ComplexNumberJpa> getEncoderDecoder() {
+    public ValueComposer<ComplexNumberJpa> getComposer() {
 // end::getEncoderDecoder[]
         // ...
 // tag::getEncoderDecoder[]
-        return new EncoderDecoder<ComplexNumberJpa>() {
+        return new ValueComposer<ComplexNumberJpa>() {
+
             @Override
-            public String toEncodedString(final ComplexNumberJpa cn) {
-                if(cn==null) {
-                    return null;
-                }
-                val re = Double.doubleToLongBits(cn.getRe());
-                val im = Double.doubleToLongBits(cn.getIm());
-                return String.format("%s:%s",
-                        Long.toHexString(re), Long.toHexString(im));
+            public ValueDecomposition decompose(final ComplexNumberJpa value) {
+                return CommonDtoUtils.typedTupleBuilder(value)
+                        .addFundamentalType(ValueType.DOUBLE, "re", ComplexNumberJpa::getRe)
+                        .addFundamentalType(ValueType.DOUBLE, "im", ComplexNumberJpa::getIm)
+                        .buildAsDecomposition();
             }
+
             @Override
-            public ComplexNumberJpa fromEncodedString(final String str) {
-                if(_NullSafe.isEmpty(str)) {
-                    return null;
-                }
-                val chunks = _Strings.splitThenStream(str, ":")
-                    .limit(2)
-                    .collect(Collectors.toList());
-                if(chunks.size()<2) {
-                    throw new IllegalArgumentException("Invalid format " + str);
-                }
-                val re = Double.longBitsToDouble(Long.parseLong(chunks.get(0), 16));
-                val im = Double.longBitsToDouble(Long.parseLong(chunks.get(1), 16));
-                return ComplexNumberJpa.of(re, im);
+            public ComplexNumberJpa compose(final ValueDecomposition decomposition) {
+                return decomposition.right()
+                        .map(CommonDtoUtils::typedTupleAsMap)
+                        .map(map->ComplexNumberJpa.of(
+                                (Double)map.get("re"),
+                                (Double)map.get("im")))
+                        .orElse(null);
             }
+
+//            @Override
+//            public String toEncodedString(final ComplexNumberJpa cn) {
+//                if(cn==null) {
+//                    return null;
+//                }
+//                val re = Double.doubleToLongBits(cn.getRe());
+//                val im = Double.doubleToLongBits(cn.getIm());
+//                return String.format("%s:%s",
+//                        Long.toHexString(re), Long.toHexString(im));
+//            }
+//            @Override
+//            public ComplexNumberJpa fromEncodedString(final String str) {
+//                if(_NullSafe.isEmpty(str)) {
+//                    return null;
+//                }
+//                val chunks = _Strings.splitThenStream(str, ":")
+//                    .limit(2)
+//                    .collect(Collectors.toList());
+//                if(chunks.size()<2) {
+//                    throw new IllegalArgumentException("Invalid format " + str);
+//                }
+//                val re = Double.longBitsToDouble(Long.parseLong(chunks.get(0), 16));
+//                val im = Double.longBitsToDouble(Long.parseLong(chunks.get(1), 16));
+//                return ComplexNumberJpa.of(re, im);
+//            }
         };
     }
 // end::getEncoderDecoder[]
diff --git a/regressiontests/stable-value/src/test/java/org/apache/isis/testdomain/value/ValueSemanticsTester.java b/regressiontests/stable-value/src/test/java/org/apache/isis/testdomain/value/ValueSemanticsTester.java
index 2ce3797..333e2a5 100644
--- a/regressiontests/stable-value/src/test/java/org/apache/isis/testdomain/value/ValueSemanticsTester.java
+++ b/regressiontests/stable-value/src/test/java/org/apache/isis/testdomain/value/ValueSemanticsTester.java
@@ -99,14 +99,8 @@ public class ValueSemanticsTester<T> {
             probe.testComposer(context, composerIfAny.get());
         }
 
-        val codecIfAny = codec(prop);
-        if(codecIfAny.isPresent()) {
-            probe.testEncoderDecoder(context, codecIfAny.get());
-        }
-
-        assertTrue(composerIfAny.isPresent()
-                || codecIfAny.isPresent(), ()->
-                        "either codec or composer must be available for "
+        assertTrue(composerIfAny.isPresent(), ()->
+                        "composer must be available for "
                             + context.getFeatureIdentifier());
 
         val parserIfAny = parser(prop);
@@ -190,12 +184,6 @@ public class ValueSemanticsTester<T> {
         return valueFacet.selectDefaultComposer();
     }
 
-    private Optional<EncoderDecoder<T>> codec(
-            final ObjectFeature feature) {
-        val valueFacet = valueFacet(feature);
-        return valueFacet.selectDefaultEncoderDecoder();
-    }
-
     private Optional<Parser<T>> parser(
             final ObjectFeature feature) {
         val valueFacet = valueFacet(feature);
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoder.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoder.java
index f935602..e0049f9 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoder.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoder.java
@@ -35,7 +35,8 @@ import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
 import org.apache.isis.applib.exceptions.recoverable.TextEntryParseException;
-import org.apache.isis.applib.value.semantics.EncoderDecoder;
+import org.apache.isis.applib.value.semantics.ValueComposer;
+import org.apache.isis.applib.value.semantics.ValueComposer.ValueDecomposition;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.collections._Maps;
 import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
@@ -146,13 +147,13 @@ public class JsonValueEncoder {
 
             final Object value = ManagedObjects.isNullOrUnspecifiedOrEmpty(objectAdapter)
                     ? NullNode.getInstance()
-                    : toEncodedString(objectSpecification, objectAdapter.getPojo())
+                    : decomposeToJson(objectSpecification, objectAdapter.getPojo())
                         .map(Object.class::cast)
                         .orElseGet(()->{
-                            log.warn("{Could not resolve an EncoderDecoder for {}, "
+                            log.warn("{Could not resolve a ValueComposer for {}, "
                                     + "falling back to rendering as 'null'. "
                                     + "Make sure the framework has access to a ValueSemanticsProvider<{}> "
-                                    + "that implements EncoderDecoder<{}>}",
+                                    + "that implements ValueComposer<{}>}",
                                     objectSpecification.getLogicalTypeName(),
                                     objectSpecification.getCorrespondingClass().getSimpleName(),
                                     objectSpecification.getCorrespondingClass().getSimpleName());
@@ -165,11 +166,17 @@ public class JsonValueEncoder {
         }
     }
 
-    private static <T> Optional<String> toEncodedString(final ObjectSpecification spec, final T pojo) {
+    @SuppressWarnings("unchecked")
+    private static <T> Optional<ValueDecomposition> decompose(final ObjectSpecification spec, final T pojo) {
         return spec.lookupFacet(ValueFacet.class)
-            .flatMap(ValueFacet::selectDefaultEncoderDecoder)
-            .map(EncoderDecoder.class::cast)
-            .map(codec->((EncoderDecoder<T>)codec).toEncodedString(pojo));
+            .flatMap(ValueFacet::selectDefaultComposer)
+            .map(ValueComposer.class::cast)
+            .<ValueDecomposition>map(composer->((ValueComposer<T>)composer).decompose(pojo));
+    }
+
+    private static <T> Optional<String> decomposeToJson(final ObjectSpecification spec, final T pojo) {
+        return decompose(spec, pojo)
+                .map(ValueDecomposition::toJson);
     }
 
     @Nullable