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/08/19 08:44:07 UTC

[isis] branch master updated: ISIS-3127: [RO] properly implement custom format for JsonValueConverter

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 5a81d38bcf ISIS-3127: [RO] properly implement custom format for JsonValueConverter
5a81d38bcf is described below

commit 5a81d38bcfe83ef88f6c3510e2a270b229317a16
Author: andi-huber <ah...@apache.org>
AuthorDate: Fri Aug 19 10:43:57 2022 +0200

    ISIS-3127: [RO] properly implement custom format for JsonValueConverter
---
 .../metamodel/spec/feature/HasObjectFeature.java   |  26 ++++
 .../isis/testdomain/rest/JsonValueEncoderTest.java |   8 +-
 .../restfulobjects/applib/JsonRepresentation.java  |  19 +++
 .../domainobjects/ActionResultReprRenderer.java    |   2 +-
 .../domainobjects/DomainObjectReprRenderer.java    |   6 +-
 .../domainobjects/JsonValueConverter.java          | 101 ++++++++++---
 .../domainobjects/JsonValueConverters.java         | 168 ++++++++++++---------
 .../rendering/domainobjects/JsonValueEncoder.java  |  18 +--
 .../domainobjects/ObjectActionReprRenderer.java    |   5 +-
 .../domainobjects/ObjectPropertyReprRenderer.java  |  25 +--
 .../domainobjects/ScalarValueReprRenderer.java     |  18 ++-
 .../JsonValueEncoderTest_appendValueAndFormat.java |  64 ++++----
 12 files changed, 296 insertions(+), 164 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/HasObjectFeature.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/HasObjectFeature.java
new file mode 100644
index 0000000000..50720d2431
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/HasObjectFeature.java
@@ -0,0 +1,26 @@
+/*
+ *  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.isis.core.metamodel.spec.feature;
+
+@FunctionalInterface
+public interface HasObjectFeature {
+
+    ObjectFeature getObjectFeature();
+
+}
diff --git a/regressiontests/stable-rest/src/test/java/org/apache/isis/testdomain/rest/JsonValueEncoderTest.java b/regressiontests/stable-rest/src/test/java/org/apache/isis/testdomain/rest/JsonValueEncoderTest.java
index 7007db236e..9d2b730c5a 100644
--- a/regressiontests/stable-rest/src/test/java/org/apache/isis/testdomain/rest/JsonValueEncoderTest.java
+++ b/regressiontests/stable-rest/src/test/java/org/apache/isis/testdomain/rest/JsonValueEncoderTest.java
@@ -32,6 +32,7 @@ import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.testdomain.conf.Configuration_headless;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.jaxrsresteasy4.IsisModuleViewerRestfulObjectsJaxrsResteasy4;
+import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.JsonValueConverter.Context;
 import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.JsonValueEncoder;
 
 import lombok.val;
@@ -53,7 +54,7 @@ class JsonValueEncoderTest {
 
         val jsonValueEncoder = JsonValueEncoder.forTesting(mmc.getSpecificationLoader());
         val representation = JsonRepresentation.newMap();
-        jsonValueEncoder.appendValueAndFormat(valueAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(valueAdapter, representation, defaultContext());
 
         System.err.printf("representation %s%n", representation);
 
@@ -68,4 +69,9 @@ class JsonValueEncoderTest {
         //assertEquals("javamathbigdecimal", representation.getString("extensions.x-isis-format"));
     }
 
+    private Context defaultContext() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
 }
diff --git a/viewers/restfulobjects/applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/JsonRepresentation.java b/viewers/restfulobjects/applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/JsonRepresentation.java
index 2722d6c6df..3d619d7ca4 100644
--- a/viewers/restfulobjects/applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/JsonRepresentation.java
+++ b/viewers/restfulobjects/applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/JsonRepresentation.java
@@ -46,6 +46,7 @@ import com.fasterxml.jackson.databind.node.POJONode;
 import org.joda.time.LocalTime;
 import org.joda.time.format.DateTimeFormatter;
 import org.joda.time.format.ISODateTimeFormat;
+import org.springframework.lang.Nullable;
 
 import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.commons.internal.collections._Maps;
@@ -1539,6 +1540,24 @@ public class JsonRepresentation {
         return this;
     }
 
+    // -- FORMATS
+
+    public JsonRepresentation putFormat(final @Nullable String format) {
+        if(format != null) {
+            mapPutString("format", format);
+        }
+        return this;
+    }
+
+    public JsonRepresentation putExtendedFormat(final @Nullable String format) {
+        if(format != null) {
+            mapPutString("extensions.x-isis-format", format);
+        }
+        return this;
+    }
+
+    // -- PATH
+
     private static class Path {
         private final List<String> head;
         private final String tail;
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ActionResultReprRenderer.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ActionResultReprRenderer.java
index c2336e96fb..f3748cbdcd 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ActionResultReprRenderer.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ActionResultReprRenderer.java
@@ -154,7 +154,7 @@ extends ReprRendererAbstract<ObjectAndActionInvocation> {
         case SCALAR_VALUE:
 
             final ScalarValueReprRenderer scalarValueReprRenderer =
-            new ScalarValueReprRenderer(resourceContext, null, representation);
+            new ScalarValueReprRenderer(resourceContext, action, null, representation);
             scalarValueReprRenderer.with(returnedAdapter)
             .withReturnType(action.getReturnType());
 
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java
index 339e7b0940..80886aa489 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/DomainObjectReprRenderer.java
@@ -33,6 +33,7 @@ import org.apache.isis.core.metamodel.spec.ManagedObjects;
 import org.apache.isis.core.metamodel.spec.feature.MixedIn;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ObjectFeature;
 import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
 import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 import org.apache.isis.core.metamodel.util.Facets;
@@ -453,13 +454,14 @@ extends ReprRendererAbstract<ManagedObject> {
 
     public static Object valueOrRef(
             final IResourceContext context,
+            final ObjectFeature objectFeature,
             final JsonValueEncoder jsonValueEncoder,
             final ManagedObject domainObject) {
 
         val spec = domainObject.getSpecification();
         if(spec.isValue()) {
-            String format = null; // TODO
-            return jsonValueEncoder.asObject(domainObject, format);
+            val context2 = JsonValueConverter.Context.of(objectFeature, context.suppressMemberExtensions());
+            return jsonValueEncoder.asObject(domainObject, context2);
         }
 
         return DomainObjectReprRenderer.newLinkToBuilder(
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueConverter.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueConverter.java
index b081babb2d..de34a36335 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueConverter.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueConverter.java
@@ -18,15 +18,24 @@
  */
 package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
 
+import java.util.OptionalInt;
+
 import com.fasterxml.jackson.databind.node.NullNode;
 
 import org.springframework.lang.Nullable;
 
 import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.internal.exceptions._Exceptions;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.ManagedObjects;
+import org.apache.isis.core.metamodel.spec.feature.HasObjectFeature;
+import org.apache.isis.core.metamodel.spec.feature.ObjectFeature;
+import org.apache.isis.core.metamodel.util.Facets;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 
 import lombok.Getter;
+import lombok.NonNull;
 
 public interface JsonValueConverter {
 
@@ -34,66 +43,112 @@ public interface JsonValueConverter {
      * The value as pojo, otherwise <tt>null</tt>.
      */
     @Nullable
-    Object recoverValueAsPojo(JsonRepresentation repr, String format);
+    Object recoverValueAsPojo(JsonRepresentation repr, Context context);
 
-    Object asObject(ManagedObject objectAdapter, String format);
+    Object asObject(ManagedObject objectAdapter, Context context);
 
     Object appendValueAndFormat(
             final ManagedObject objectAdapter,
-            final String formatOverride,
-            final JsonRepresentation repr,
-            final boolean suppressExtensions);
+            final Context context,
+            final JsonRepresentation repr);
 
     Can<Class<?>> getClasses();
 
+    static interface Context extends HasObjectFeature {
+
+        OptionalInt maxTotalDigits(@Nullable ManagedObject value);
+        OptionalInt maxFractionalDigits(@Nullable ManagedObject value);
+        boolean isSuppressExtensions();
+
+        public static Context of(
+                final @NonNull ObjectFeature objectFeature,
+                final boolean suppressExtensions) {
+            return new Context.InferredFromFacets(objectFeature, suppressExtensions);
+        }
+
+        public static Context forTesting(final Integer maxTotalDigits, final Integer maxFractionalDigits) {
+            return new Context() {
+                @Override public ObjectFeature getObjectFeature() {
+                    throw _Exceptions.notImplemented(); }
+                @Override public OptionalInt maxTotalDigits(final ManagedObject value) {
+                    return OptionalInt.of(maxTotalDigits); }
+                @Override public OptionalInt maxFractionalDigits(final ManagedObject value) {
+                    return OptionalInt.of(maxFractionalDigits); }
+                @Override public boolean isSuppressExtensions() {
+                    return false; }
+            };
+        }
+
+        @Getter
+        @lombok.Value
+        static class InferredFromFacets implements Context {
+            final @NonNull ObjectFeature objectFeature;
+            final boolean suppressExtensions;
+
+            @Override
+            public OptionalInt maxTotalDigits(final @Nullable ManagedObject value) {
+                return Facets.maxTotalDigits(facetHolders(value));
+            }
+
+            @Override
+            public OptionalInt maxFractionalDigits(final @Nullable ManagedObject value) {
+                return Facets.maxFractionalDigits(facetHolders(value));
+            }
+
+            // look for facet on feature, else on the value's spec
+            private Can<FacetHolder> facetHolders(final @Nullable ManagedObject value) {
+                return ManagedObjects.isNullOrUnspecifiedOrEmpty(value)
+                    ? Can.of(objectFeature)
+                    : Can.of(objectFeature, value.getSpecification());
+            }
+
+        }
+
+    }
+
     static abstract class Abstract implements JsonValueConverter {
         protected final String format;
-        protected final String xIsisFormat;
+        protected final String extendedFormat;
 
         @Getter private final Can<Class<?>> classes;
 
-        public Abstract(final String format, final String xIsisFormat, final Class<?>... classes) {
+        public Abstract(final String format, final String extendedFormat, final Class<?>... classes) {
             this.format = format;
-            this.xIsisFormat = xIsisFormat;
+            this.extendedFormat = extendedFormat;
             this.classes = Can.ofArray(classes);
         }
 
         @Override
         public Object appendValueAndFormat(
                 final ManagedObject objectAdapter,
-                final String formatOverride,
-                final JsonRepresentation repr,
-                final boolean suppressExtensions) {
+                final Context context,
+                final JsonRepresentation repr) {
 
             final Object value = unwrapAsObjectElseNullNode(objectAdapter);
             repr.mapPut("value", value);
-            appendFormats(repr, effectiveFormat(formatOverride), this.xIsisFormat, suppressExtensions);
+            appendFormats(repr, context);
             return value;
         }
 
         @Override
-        public final Object asObject(final ManagedObject objectAdapter, final String format) {
+        public final Object asObject(final ManagedObject objectAdapter, final Context format) {
             return objectAdapter.getPojo();
         }
 
         protected final String effectiveFormat(final String formatOverride) {
-            return formatOverride!=null ? formatOverride : this.format;
+            return formatOverride!=null ? formatOverride : format;
         }
 
         static Object unwrapAsObjectElseNullNode(final ManagedObject adapter) {
             return adapter != null? adapter.getPojo(): NullNode.getInstance();
         }
 
-        static void appendFormats(
+        void appendFormats(
                 final JsonRepresentation repr,
-                final String format,
-                final String xIsisFormat,
-                final boolean suppressExtensions) {
-            if(format != null) {
-                repr.mapPutString("format", format);
-            }
-            if(!suppressExtensions && xIsisFormat != null) {
-                repr.mapPutString("extensions.x-isis-format", xIsisFormat);
+                final Context context) {
+            repr.putFormat(format);
+            if(!context.isSuppressExtensions()) {
+                repr.putExtendedFormat(extendedFormat);
             }
         }
     }
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueConverters.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueConverters.java
index 9b9c28a942..1c22952e2f 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueConverters.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueConverters.java
@@ -48,50 +48,51 @@ public final class JsonValueConverters {
 
         converters.add(new JsonValueConverter.Abstract(null, "string", String.class){
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isString()) {
                     return repr.asString();
                 }
                 return null;
             }
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride, final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 val obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof String) {
                     repr.mapPutString("value", (String) obj);
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
 
         converters.add(new JsonValueConverter.Abstract(null, "boolean", boolean.class, Boolean.class){
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isBoolean()) {
                     return repr.asBoolean();
                 }
                 return null;
             }
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 val obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof Boolean) {
                     repr.mapPutBooleanNullable("value", (Boolean) obj);
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
 
         converters.add(new JsonValueConverter.Abstract("int", "byte", byte.class, Byte.class){
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isNumber()) {
                     return repr.asNumber().byteValue();
                 }
@@ -107,22 +108,22 @@ public final class JsonValueConverters {
                 return null;
             }
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 val obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof Byte) {
                     repr.mapPutByteNullable("value", (Byte) obj);
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
 
         converters.add(new JsonValueConverter.Abstract("int", "short", short.class, Short.class){
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isNumber()) {
                     return repr.asNumber().shortValue();
                 }
@@ -138,22 +139,22 @@ public final class JsonValueConverters {
                 return null;
             }
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof Short) {
                     repr.mapPutShortNullable("value", (Short) obj);
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
 
         converters.add(new JsonValueConverter.Abstract("int", "int", int.class, Integer.class){
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isInt()) {
                     return repr.asInt();
                 }
@@ -169,22 +170,22 @@ public final class JsonValueConverters {
                 return null;
             }
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof Integer) {
                     repr.mapPutIntNullable("value", (Integer) obj);
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
 
         converters.add(new JsonValueConverter.Abstract("int", "long", long.class, Long.class){
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isLong()) {
                     return repr.asLong();
                 }
@@ -200,8 +201,8 @@ public final class JsonValueConverters {
                 return null;
             }
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof Long) {
                     final Long l = (Long) obj;
@@ -209,14 +210,14 @@ public final class JsonValueConverters {
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
 
         converters.add(new JsonValueConverter.Abstract("decimal", "float", float.class, Float.class){
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isDecimal()) {
                     return repr.asDouble().floatValue();
                 }
@@ -235,8 +236,8 @@ public final class JsonValueConverters {
                 return null;
             }
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof Float) {
                     final Float f = (Float) obj;
@@ -244,14 +245,14 @@ public final class JsonValueConverters {
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
 
         converters.add(new JsonValueConverter.Abstract("decimal", "double", double.class, Double.class){
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isDecimal()) {
                     return repr.asDouble();
                 }
@@ -273,8 +274,8 @@ public final class JsonValueConverters {
                 return null;
             }
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof Double) {
                     final Double d = (Double) obj;
@@ -282,14 +283,14 @@ public final class JsonValueConverters {
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
 
         converters.add(new JsonValueConverter.Abstract(null, "char", char.class, Character.class){
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isString()) {
                     final String str = repr.asString();
                     if(str != null && str.length()>0) {
@@ -307,8 +308,8 @@ public final class JsonValueConverters {
                 return null;
             }
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof Character) {
                     final Character c = (Character) obj;
@@ -316,14 +317,14 @@ public final class JsonValueConverters {
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
 
         converters.add(new JsonValueConverter.Abstract("big-integer(18)", "javamathbiginteger", BigInteger.class){
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isString()) {
                     return new BigInteger(repr.asString());
                 }
@@ -342,23 +343,31 @@ public final class JsonValueConverters {
                 return null;
             }
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof BigInteger) {
                     final BigInteger bi = (BigInteger) obj;
                     repr.mapPutBigInteger("value", bi);
+                    // custom format if constrained
+                    context.maxTotalDigits(objectAdapter)
+                    .ifPresentOrElse(
+                            totalDigits->repr.putFormat(String.format("big-integer(%d)", totalDigits)),
+                            ()->repr.putFormat(format));
                 } else {
                     repr.mapPut("value", obj);
+                    repr.putFormat(format);
+                }
+                if(!context.isSuppressExtensions()) {
+                    repr.putExtendedFormat(extendedFormat);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
                 return obj;
             }
         });
 
         converters.add(new JsonValueConverter.Abstract("big-decimal", "javamathbigdecimal", BigDecimal.class){
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isString()) {
                     return new BigDecimal(repr.asString());
                 }
@@ -380,16 +389,33 @@ public final class JsonValueConverters {
                 return null;
             }
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof BigDecimal) {
                     final BigDecimal bd = (BigDecimal) obj;
                     repr.mapPutBigDecimal("value", bd);
+
+                    // custom format if constrained
+                    final int totalDigits = context.maxTotalDigits(objectAdapter).orElse(-1);
+                    final int scale = context.maxFractionalDigits(objectAdapter).orElse(-1);
+                    if(totalDigits>-1
+                            && scale>-1) {
+                        val formatOverride = String.format("big-decimal(%d,%d)", totalDigits, scale);
+                        repr.putFormat(formatOverride);
+                    } else {
+                        repr.putFormat(format);
+                    }
+
                 } else {
                     repr.mapPut("value", obj);
+                    repr.putFormat(format);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+
+                if(!context.isSuppressExtensions()) {
+                    repr.putExtendedFormat(extendedFormat);
+                }
+
                 return obj;
             }
         });
@@ -405,7 +431,7 @@ public final class JsonValueConverters {
                     );
 
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isString()) {
                     final String dateStr = repr.asString();
                     for (DateTimeFormatter formatter : formatters) {
@@ -421,8 +447,8 @@ public final class JsonValueConverters {
             }
 
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof LocalDate) {
                     final LocalDate date = (LocalDate) obj;
@@ -431,7 +457,7 @@ public final class JsonValueConverters {
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
@@ -447,7 +473,7 @@ public final class JsonValueConverters {
                     );
 
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isString()) {
                     final String dateStr = repr.asString();
                     for (DateTimeFormatter formatter : formatters) {
@@ -463,8 +489,8 @@ public final class JsonValueConverters {
             }
 
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof LocalDateTime) {
                     final LocalDateTime date = (LocalDateTime) obj;
@@ -473,7 +499,7 @@ public final class JsonValueConverters {
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
@@ -489,7 +515,7 @@ public final class JsonValueConverters {
                     );
 
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isString()) {
                     final String dateStr = repr.asString();
                     for (DateTimeFormatter formatter : formatters) {
@@ -505,8 +531,8 @@ public final class JsonValueConverters {
             }
 
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof DateTime) {
                     final DateTime date = (DateTime) obj;
@@ -515,7 +541,7 @@ public final class JsonValueConverters {
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
@@ -530,7 +556,7 @@ public final class JsonValueConverters {
                     JsonRepresentation.yyyyMMddTHHmmssZ.withZoneUTC()
                     );
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isString()) {
                     final String dateStr = repr.asString();
                     for (DateTimeFormatter formatter : formatters) {
@@ -547,8 +573,8 @@ public final class JsonValueConverters {
             }
 
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof java.util.Date) {
                     final java.util.Date date = (java.util.Date) obj;
@@ -558,7 +584,7 @@ public final class JsonValueConverters {
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
@@ -571,7 +597,7 @@ public final class JsonValueConverters {
                     JsonRepresentation.yyyyMMdd.withZoneUTC()
                     );
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isString()) {
                     final String dateStr = repr.asString();
                     for (DateTimeFormatter formatter : formatters) {
@@ -588,8 +614,8 @@ public final class JsonValueConverters {
             }
 
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof java.sql.Date) {
                     final java.sql.Date date = (java.sql.Date) obj;
@@ -598,7 +624,7 @@ public final class JsonValueConverters {
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
@@ -612,7 +638,7 @@ public final class JsonValueConverters {
                     JsonRepresentation._HHmmss.withZoneUTC()
                     );
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isString()) {
                     final String dateStr = repr.asString();
                     for (DateTimeFormatter formatter : formatters) {
@@ -629,8 +655,8 @@ public final class JsonValueConverters {
             }
 
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof java.sql.Time) {
                     final java.sql.Time date = (java.sql.Time) obj;
@@ -639,7 +665,7 @@ public final class JsonValueConverters {
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
@@ -647,7 +673,7 @@ public final class JsonValueConverters {
         converters.add(new JsonValueConverter.Abstract("utc-millisec", "javasqltimestamp", java.sql.Timestamp.class){
 
             @Override
-            public Object recoverValueAsPojo(final JsonRepresentation repr, final String format) {
+            public Object recoverValueAsPojo(final JsonRepresentation repr, final Context context) {
                 if (repr.isLong()) {
                     final Long millis = repr.asLong();
                     final java.sql.Timestamp parsedTimestamp = new java.sql.Timestamp(millis);
@@ -667,8 +693,8 @@ public final class JsonValueConverters {
             }
 
             @Override
-            public Object appendValueAndFormat(final ManagedObject objectAdapter, final String formatOverride,
-                    final JsonRepresentation repr, final boolean suppressExtensions) {
+            public Object appendValueAndFormat(final ManagedObject objectAdapter, final Context context,
+                    final JsonRepresentation repr) {
                 final Object obj = unwrapAsObjectElseNullNode(objectAdapter);
                 if(obj instanceof java.sql.Timestamp) {
                     final java.sql.Timestamp date = (java.sql.Timestamp) obj;
@@ -677,7 +703,7 @@ public final class JsonValueConverters {
                 } else {
                     repr.mapPut("value", obj);
                 }
-                appendFormats(repr, effectiveFormat(formatOverride), xIsisFormat, suppressExtensions);
+                appendFormats(repr, context);
                 return obj;
             }
         });
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 389453f6ca..29ebe75817 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
@@ -45,6 +45,7 @@ import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.util.Facets;
 import org.apache.isis.viewer.restfulobjects.applib.IsisModuleViewerRestfulObjectsApplib;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.JsonValueConverter.Context;
 
 import lombok.NonNull;
 import lombok.val;
@@ -78,7 +79,7 @@ public class JsonValueEncoder {
     public ManagedObject asAdapter(
             final ObjectSpecification objectSpec,
             final JsonRepresentation argValueRepr,
-            final String format) {
+            final JsonValueConverter.Context context) {
 
         if(argValueRepr == null) {
             return null;
@@ -105,7 +106,7 @@ public class JsonValueEncoder {
             throw new IllegalArgumentException("Unable to parse value");
         }
 
-        val valueAsPojo = jvc.recoverValueAsPojo(argValueRepr, format);
+        val valueAsPojo = jvc.recoverValueAsPojo(argValueRepr, context);
         if(valueAsPojo != null) {
             return ManagedObject.lazy(specificationLoader, valueAsPojo);
         }
@@ -127,24 +128,23 @@ public class JsonValueEncoder {
     public Object appendValueAndFormat(
             final ManagedObject valueAdapter,
             final JsonRepresentation repr,
-            final String formatOverride,
-            final boolean suppressExtensions) {
+            final Context context) {
 
         val valueSpec = valueAdapter.getSpecification();
         val valueClass = valueSpec.getCorrespondingClass();
         val jsonValueConverter = converterByClass.get(valueClass);
         if(jsonValueConverter != null) {
-            return jsonValueConverter.appendValueAndFormat(valueAdapter, formatOverride, repr, suppressExtensions);
+            return jsonValueConverter.appendValueAndFormat(valueAdapter, context, repr);
         } else {
             final Optional<ValueDecomposition> valueDecompositionIfAny = decompose(valueAdapter);
 
             if(valueDecompositionIfAny.isPresent()) {
                 val value = valueDecompositionIfAny.get().toJson();
                 repr.mapPutString("value", value);
-                appendFormats(repr, "string", "string", suppressExtensions);
+                appendFormats(repr, "string", "string", context.isSuppressExtensions());
                 return value;
             }
-            return appendNullAndFormat(repr, suppressExtensions);
+            return appendNullAndFormat(repr, context.isSuppressExtensions());
         }
     }
 
@@ -176,14 +176,14 @@ public class JsonValueEncoder {
     }
 
     @Nullable
-    public Object asObject(final @NonNull ManagedObject adapter, final String format) {
+    public Object asObject(final @NonNull ManagedObject adapter, final JsonValueConverter.Context context) {
 
         val objectSpec = adapter.getSpecification();
         val cls = objectSpec.getCorrespondingClass();
 
         val jsonValueConverter = converterByClass.get(cls);
         if(jsonValueConverter != null) {
-            return jsonValueConverter.asObject(adapter, format);
+            return jsonValueConverter.asObject(adapter, context);
         }
 
         // else
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java
index f3d58bdaf2..00c945c3bc 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectActionReprRenderer.java
@@ -172,7 +172,7 @@ extends AbstractObjectMemberReprRenderer<ObjectAction> {
         for (val choiceAdapter : choiceAdapters) {
             // REVIEW: previously was using the spec of the parameter, but think instead it should be the spec of the adapter itself
             // final ObjectSpecification choiceSpec = param.getSpecification();
-            list.add(DomainObjectReprRenderer.valueOrRef(resourceContext, super.getJsonValueEncoder(), choiceAdapter));
+            list.add(DomainObjectReprRenderer.valueOrRef(resourceContext, paramMeta, getJsonValueEncoder(), choiceAdapter));
         }
         return list;
     }
@@ -184,7 +184,8 @@ extends AbstractObjectMemberReprRenderer<ObjectAction> {
         }
         // REVIEW: previously was using the spec of the parameter, but think instead it should be the spec of the adapter itself
         // final ObjectSpecification defaultSpec = param.getSpecification();
-        return DomainObjectReprRenderer.valueOrRef(resourceContext, super.getJsonValueEncoder(), defaultAdapter);
+        val paramMeta = paramMod.getMetaModel();
+        return DomainObjectReprRenderer.valueOrRef(resourceContext, paramMeta, getJsonValueEncoder(), defaultAdapter);
     }
 
     // ///////////////////////////////////////////////////
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
index 717a61d4e2..cddfeeb9ab 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ObjectPropertyReprRenderer.java
@@ -23,9 +23,7 @@ import java.util.List;
 import com.fasterxml.jackson.databind.node.NullNode;
 
 import org.apache.isis.applib.annotation.Where;
-import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.collections._Lists;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.interactions.managed.ManagedProperty;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ManagedObjects;
@@ -91,30 +89,11 @@ extends AbstractObjectMemberReprRenderer<OneToOneAssociation> {
         val spec = valueAdapter.getSpecification();
 
         if (spec.isValue()) {
-            String formatOverride = null;
-            final Class<?> valueType = spec.getCorrespondingClass();
-            if(valueType == java.math.BigDecimal.class) {
-
-                // look for facet on member, else on the value's spec
-                val facetHolders = Can.<FacetHolder>of(objectMember, spec);
-                final int totalDigits = Facets.maxTotalDigits(facetHolders).orElse(-1);
-                final int scale = Facets.maxFractionalDigits(facetHolders).orElse(-1);
-
-                formatOverride = String.format("big-decimal(%d,%d)", totalDigits, scale);
-
-            } else if(valueType == java.math.BigInteger.class) {
-
-                // look for facet on member, else on the value's spec
-                val facetHolders = Can.<FacetHolder>of(objectMember, spec);
-                final int totalDigits = Facets.maxTotalDigits(facetHolders).orElse(-1);
-                formatOverride = String.format("big-integer(%d)", totalDigits);
-            }
             return jsonValueEncoder
                     .appendValueAndFormat(
                             valueAdapter,
                             representation,
-                            formatOverride,
-                            resourceContext.suppressMemberExtensions());
+                            JsonValueConverter.Context.of(objectMember, resourceContext.suppressMemberExtensions()));
         }
 
         if(valueAdapter.getPojo() == null) {
@@ -204,7 +183,7 @@ extends AbstractObjectMemberReprRenderer<OneToOneAssociation> {
             // final ObjectSpecification choiceSpec = objectMember.getSpecification();
 
             // REVIEW: check that it works for ToDoItem$Category, though...
-            list.add(DomainObjectReprRenderer.valueOrRef(resourceContext, super.getJsonValueEncoder(), choiceAdapter));
+            list.add(DomainObjectReprRenderer.valueOrRef(resourceContext, objectMember, super.getJsonValueEncoder(), choiceAdapter));
         }
         return list;
     }
diff --git a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ScalarValueReprRenderer.java b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ScalarValueReprRenderer.java
index cec006c73e..8fb24bacc6 100644
--- a/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ScalarValueReprRenderer.java
+++ b/viewers/restfulobjects/rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/ScalarValueReprRenderer.java
@@ -22,6 +22,8 @@ import javax.ws.rs.core.MediaType;
 
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.HasObjectFeature;
+import org.apache.isis.core.metamodel.spec.feature.ObjectFeature;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.Rel;
 import org.apache.isis.viewer.restfulobjects.rendering.IResourceContext;
@@ -29,17 +31,24 @@ import org.apache.isis.viewer.restfulobjects.rendering.LinkFollowSpecs;
 import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererAbstract;
 import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererException;
 
+import lombok.Getter;
+import lombok.val;
+
 public class ScalarValueReprRenderer
-extends ReprRendererAbstract<ManagedObject> {
+extends ReprRendererAbstract<ManagedObject>
+implements HasObjectFeature {
 
     private ObjectSpecification returnType;
+    @Getter(onMethod_ = {@Override}) private ObjectFeature objectFeature;
 
     public ScalarValueReprRenderer(
             final IResourceContext resourceContext,
+            final ObjectFeature objectFeature,
             final LinkFollowSpecs linkFollower,
             final JsonRepresentation representation) {
         // null for representationType (there is none)
         super(resourceContext, linkFollower, null, representation);
+        this.objectFeature = objectFeature;
     }
 
     /**
@@ -58,8 +67,11 @@ extends ReprRendererAbstract<ManagedObject> {
         if (!objectAdapter.getSpecification().isValue()) {
             throw ReprRendererException.create("Not an (encodable) value", objectAdapter.titleString());
         }
-        String format = null; // TODO
-        final Object value = jsonValueEncoder.asObject(objectAdapter, format);
+
+        val context = JsonValueConverter.Context.of(
+                getObjectFeature(),
+                getResourceContext().suppressMemberExtensions());
+        final Object value = jsonValueEncoder.asObject(objectAdapter, context);
 
         representation.mapPut("value", value);
         return this;
diff --git a/viewers/restfulobjects/rendering/src/test/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoderTest_appendValueAndFormat.java b/viewers/restfulobjects/rendering/src/test/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoderTest_appendValueAndFormat.java
index 4d751f33cc..ab7c5c312b 100644
--- a/viewers/restfulobjects/rendering/src/test/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoderTest_appendValueAndFormat.java
+++ b/viewers/restfulobjects/rendering/src/test/java/org/apache/isis/viewer/restfulobjects/rendering/domainobjects/JsonValueEncoderTest_appendValueAndFormat.java
@@ -30,10 +30,6 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.nullValue;
-
 import org.apache.isis.applib.id.LogicalType;
 import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2;
 import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
@@ -41,6 +37,11 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
+import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.JsonValueConverter.Context;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
 
 public class JsonValueEncoderTest_appendValueAndFormat {
 
@@ -73,7 +74,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(String.class);
         allowingObjectAdapterToReturn("aString");
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isString("value"), is(true));
         assertThat(representation.getString("value"), is("aString"));
 
@@ -86,7 +87,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(Boolean.class);
         allowingObjectAdapterToReturn(Boolean.TRUE);
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isBoolean("value"), is(true));
         assertThat(representation.getBoolean("value"), is(Boolean.TRUE));
 
@@ -98,7 +99,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(boolean.class);
         allowingObjectAdapterToReturn(true);
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isBoolean("value"), is(true));
         assertThat(representation.getBoolean("value"), is(true));
 
@@ -110,7 +111,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(Byte.class);
         allowingObjectAdapterToReturn(Byte.valueOf((byte)123));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isIntegralNumber("value"), is(true));
         assertThat(representation.getByte("value"), is(Byte.valueOf((byte)123)));
 
@@ -123,7 +124,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(byte.class);
         allowingObjectAdapterToReturn((byte)123);
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isIntegralNumber("value"), is(true));
         assertThat(representation.getByte("value"), is((byte)123));
 
@@ -136,7 +137,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(Short.class);
         allowingObjectAdapterToReturn(Short.valueOf((short)12345));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isIntegralNumber("value"), is(true));
         assertThat(representation.getShort("value"), is(Short.valueOf((short)12345)));
 
@@ -149,7 +150,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(short.class);
         allowingObjectAdapterToReturn((short)12345);
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isIntegralNumber("value"), is(true));
         assertThat(representation.getShort("value"), is((short)12345));
 
@@ -162,7 +163,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(Integer.class);
         allowingObjectAdapterToReturn(Integer.valueOf(12345678));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isIntegralNumber("value"), is(true));
         assertThat(representation.getInt("value"), is(Integer.valueOf(12345678)));
 
@@ -175,7 +176,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(int.class);
         allowingObjectAdapterToReturn(12345678);
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isIntegralNumber("value"), is(true));
         assertThat(representation.getInt("value"), is(12345678));
 
@@ -188,7 +189,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(Long.class);
         allowingObjectAdapterToReturn(Long.valueOf(12345678901234L));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isIntegralNumber("value"), is(true));
         assertThat(representation.getLong("value"), is(Long.valueOf(12345678901234L)));
 
@@ -201,7 +202,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(long.class);
         allowingObjectAdapterToReturn(12345678901234L);
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isIntegralNumber("value"), is(true));
         assertThat(representation.getLong("value"), is(12345678901234L));
 
@@ -214,7 +215,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(Float.class);
         allowingObjectAdapterToReturn(Float.valueOf((float)123.45));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isDecimal("value"), is(true));
         assertThat(representation.getFloat("value"), is(Float.valueOf((float)123.45)));
 
@@ -227,7 +228,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(Float.class);
         allowingObjectAdapterToReturn((float)123.45);
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isDecimal("value"), is(true));
         assertThat(representation.getFloat("value"), is((float)123.45));
 
@@ -240,7 +241,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(Double.class);
         allowingObjectAdapterToReturn(Double.valueOf(12345.6789));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isDecimal("value"), is(true));
         assertThat(representation.getDouble("value"), is(Double.valueOf(12345.6789)));
 
@@ -253,7 +254,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(double.class);
         allowingObjectAdapterToReturn(12345.6789);
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isDecimal("value"), is(true));
         assertThat(representation.getDouble("value"), is(12345.6789));
 
@@ -266,7 +267,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(Character.class);
         allowingObjectAdapterToReturn(Character.valueOf('a'));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isString("value"), is(true));
         assertThat(representation.getChar("value"), is(Character.valueOf('a')));
 
@@ -279,7 +280,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(char.class);
         allowingObjectAdapterToReturn('a');
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isString("value"), is(true));
         assertThat(representation.getChar("value"), is('a'));
 
@@ -292,7 +293,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(java.util.Date.class);
         allowingObjectAdapterToReturn(asDateTime("2014-04-25T12:34:45Z"));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isString("value"), is(true));
         assertThat(representation.getString("value"), is("2014-04-25T12:34:45Z"));
 
@@ -305,7 +306,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(java.sql.Date.class);
         allowingObjectAdapterToReturn(asSqlDate("2014-04-25"));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isString("value"), is(true));
         assertThat(representation.getString("value"), is("2014-04-25"));
 
@@ -318,7 +319,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(org.joda.time.DateTime.class);
         allowingObjectAdapterToReturn(new org.joda.time.DateTime(asDateTime("2014-04-25T12:34:45Z")));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isString("value"), is(true));
         assertThat(representation.getString("value"), is("2014-04-25T12:34:45Z"));
 
@@ -331,7 +332,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(org.joda.time.LocalDateTime.class);
         allowingObjectAdapterToReturn(new org.joda.time.LocalDateTime(asDateTime("2014-04-25T12:34:45Z")));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isString("value"), is(true));
         assertThat(representation.getString("value"), is("2014-04-25T12:34:45Z"));
 
@@ -344,7 +345,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(org.joda.time.LocalDate.class);
         allowingObjectAdapterToReturn(new org.joda.time.LocalDate(2014,4,25));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isString("value"), is(true));
         assertThat(representation.getString("value"), is("2014-04-25"));
 
@@ -358,7 +359,7 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         final long time = asDateTime("2014-04-25T12:34:45Z").getTime();
         allowingObjectAdapterToReturn(new Timestamp(time));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, null, false);
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, defaultContext());
         assertThat(representation.isLong("value"), is(true));
         assertThat(representation.getLong("value"), is(time));
 
@@ -371,7 +372,8 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(BigInteger.class);
         allowingObjectAdapterToReturn(new BigInteger("12345678901234567890"));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, "big-integer(22)", false);
+        //"big-integer(22)"
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, Context.forTesting(22, 0));
         assertThat(representation.isString("value"), is(true));
         assertThat(representation.isBigInteger("value"), is(true));
         assertThat(representation.getBigInteger("value"), is(new BigInteger("12345678901234567890")));
@@ -385,7 +387,8 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         allowingLogicalTypeReturnObjectTypeFor(BigDecimal.class);
         allowingObjectAdapterToReturn(new BigDecimal("12345678901234567890.1234"));
 
-        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, "big-decimal(27,4)", false);
+        //"big-decimal(27,4)"
+        jsonValueEncoder.appendValueAndFormat(mockObjectAdapter, representation, Context.forTesting(27, 4));
         assertThat(representation.isString("value"), is(true));
         assertThat(representation.isBigDecimal("value"), is(true));
         assertThat(representation.getBigDecimal("value"), is(new BigDecimal("12345678901234567890.1234")));
@@ -394,6 +397,9 @@ public class JsonValueEncoderTest_appendValueAndFormat {
         assertThat(representation.getString("extensions.x-isis-format"), is("javamathbigdecimal"));
     }
 
+    private Context defaultContext() {
+        return Context.forTesting(null, null);
+    }
 
     private void allowingLogicalTypeReturnObjectTypeFor(final Class<?> cls) {
         context.checking(new Expectations() {