You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by rm...@apache.org on 2019/08/10 10:41:17 UTC

[johnzon] branch master updated: JOHNZON-230 JOHNZON-231 JOHNZON-232 JOHNZON-233 JOHNZON-234 date/number format inheritance from class/package + JSON-B ijson support

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 394445e  JOHNZON-230 JOHNZON-231 JOHNZON-232 JOHNZON-233 JOHNZON-234 date/number format inheritance from class/package + JSON-B ijson support
394445e is described below

commit 394445e113affb70f3d019ebef2afac3ac577da2
Author: Romain Manni-Bucau <rm...@apache.org>
AuthorDate: Sat Aug 10 12:41:05 2019 +0200

    JOHNZON-230 JOHNZON-231 JOHNZON-232 JOHNZON-233 JOHNZON-234 date/number format inheritance from class/package + JSON-B ijson support
---
 .../org/apache/johnzon/jsonb/JohnzonBuilder.java   |  48 ++++--
 .../org/apache/johnzon/jsonb/JohnzonJsonb.java     |  32 +++-
 .../org/apache/johnzon/jsonb/JsonbAccessMode.java  |  52 +++++-
 .../jsonb/converter/JsonbDateConverter.java        |  68 +++++++-
 .../serializer/JohnzonDeserializationContext.java  |   6 +-
 .../org/apache/johnzon/jsonb/DateFormatTest.java   | 104 ++++++++++++
 .../java/org/apache/johnzon/jsonb/IJsonTest.java   | 122 ++++++++++++++
 .../org/apache/johnzon/jsonb/NumberFormatTest.java |  53 ++++++
 .../java/org/apache/johnzon/jsonb/OrderTest.java   | 179 +++++++++++++++++++++
 .../org/apache/johnzon/jsonb/model/Holder.java     |  24 +++
 .../model/packageformat/FormatFromClassModel.java  |  25 +++
 .../packageformat/FormatFromPackageModel.java}     |  22 +--
 .../model/packageformat/FormatOnClassModel.java}   |  25 ++-
 .../jsonb/model/packageformat/package-info.java}   |  25 +--
 .../johnzon/mapper/DynamicMappingGenerator.java    |   3 +-
 .../johnzon/mapper/access/FieldAccessMode.java     |   4 +-
 .../org/apache/johnzon/mapper/access/Meta.java     |  67 ++++++--
 .../johnzon/mapper/access/MethodAccessMode.java    |   4 +-
 18 files changed, 762 insertions(+), 101 deletions(-)

diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
index b06b29f..9dcbb59 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
@@ -139,6 +139,19 @@ public class JohnzonBuilder implements JsonbBuilder {
             config = new JsonbConfig();
         }
 
+        final boolean ijson = config.getProperty(JsonbConfig.STRICT_IJSON)
+                .map(Boolean.class::cast)
+                .filter(it -> it)
+                .map(it -> {
+                    if (!config.getProperty(JsonbConfig.BINARY_DATA_STRATEGY).isPresent()) {
+                        config.withBinaryDataStrategy(BinaryDataStrategy.BASE_64);
+                    }
+                    if (!config.getProperty(JsonbConfig.DATE_FORMAT).isPresent()) {
+                        config.withDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'xxx", Locale.ROOT);
+                    }
+                    return it;
+                }).orElse(false);
+
         if (config.getProperty(JsonbConfig.FORMATTING).map(Boolean.class::cast).orElse(false)) {
             builder.setPretty(true);
         }
@@ -222,9 +235,6 @@ public class JohnzonBuilder implements JsonbBuilder {
             defaultConverters.put(new AdapterKey(args[0], args[1]), johnzonJsonbAdapter);
         }));
 
-        config.getProperty(JsonbConfig.STRICT_IJSON).map(Boolean.class::cast).ifPresent(ijson -> {
-            // no-op: https://tools.ietf.org/html/rfc7493 the only MUST of the spec should be fine by default
-        });
         config.getProperty("johnzon.fail-on-unknown-properties")
                 .map(v -> Boolean.class.isInstance(v) ? Boolean.class.cast(v) : Boolean.parseBoolean(String.valueOf(v)))
                 .ifPresent(builder::setFailOnUnknownProperties);
@@ -316,7 +326,7 @@ public class JohnzonBuilder implements JsonbBuilder {
         }
         final Mapper mapper = builder.build();
 
-        return useCdi ? new JohnzonJsonb(mapper) {
+        return useCdi ? new JohnzonJsonb(mapper, ijson) {
             {
                 cdiIntegration.track(this);
             }
@@ -331,7 +341,7 @@ public class JohnzonBuilder implements JsonbBuilder {
                     }
                 }
             }
-        } : new JohnzonJsonb(mapper);
+        } : new JohnzonJsonb(mapper, ijson);
     }
     
 
@@ -441,14 +451,15 @@ public class JohnzonBuilder implements JsonbBuilder {
         converters.put(new AdapterKey(Calendar.class, String.class), new ConverterAdapter<>(new Converter<Calendar>() {
             @Override
             public String toString(final Calendar instance) {
-                return ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC).toString();
+                return ZonedDateTime.ofInstant(instance.toInstant(), instance.getTimeZone().toZoneId()).toString();
             }
 
             @Override
             public Calendar fromString(final String text) {
+                final ZonedDateTime zonedDateTime = ZonedDateTime.parse(text);
                 final Calendar calendar = Calendar.getInstance();
-                calendar.setTimeZone(timeZoneUTC);
-                calendar.setTimeInMillis(ZonedDateTime.parse(text).toInstant().toEpochMilli());
+                calendar.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone()));
+                calendar.setTimeInMillis(zonedDateTime.toInstant().toEpochMilli());
                 return calendar;
             }
         }));
@@ -460,9 +471,10 @@ public class JohnzonBuilder implements JsonbBuilder {
 
             @Override
             public GregorianCalendar fromString(final String text) {
+                final ZonedDateTime zonedDateTime = ZonedDateTime.parse(text);
                 final GregorianCalendar calendar = new GregorianCalendar();
-                calendar.setTimeZone(timeZoneUTC);
-                calendar.setTimeInMillis(ZonedDateTime.parse(text).toInstant().toEpochMilli());
+                calendar.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone()));
+                calendar.setTimeInMillis(zonedDateTime.toInstant().toEpochMilli());
                 return calendar;
             }
         }));
@@ -687,13 +699,15 @@ public class JohnzonBuilder implements JsonbBuilder {
 
                 @Override
                 public String toString(final Calendar instance) {
-                    return formatter.format(ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC));
+                    return formatter.format(ZonedDateTime.ofInstant(instance.toInstant(), instance.getTimeZone().toZoneId()));
                 }
 
                 @Override
                 public Calendar fromString(final String text) {
-                    Calendar instance = Calendar.getInstance();
-                    instance.setTime(Date.from(parseZonedDateTime(text, formatter, zoneIDUTC).toInstant()));
+                    final ZonedDateTime zonedDateTime = parseZonedDateTime(text, formatter, zoneIDUTC);
+                    final Calendar instance = Calendar.getInstance();
+                    instance.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone()));
+                    instance.setTime(Date.from(zonedDateTime.toInstant()));
                     return instance;
                 }
             }));
@@ -701,13 +715,15 @@ public class JohnzonBuilder implements JsonbBuilder {
 
                 @Override
                 public String toString(final GregorianCalendar instance) {
-                    return formatter.format(ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC));
+                    return formatter.format(ZonedDateTime.ofInstant(instance.toInstant(), instance.getTimeZone().toZoneId()));
                 }
 
                 @Override
                 public GregorianCalendar fromString(final String text) {
-                    Calendar instance = GregorianCalendar.getInstance();
-                    instance.setTime(Date.from(parseZonedDateTime(text, formatter, zoneIDUTC).toInstant()));
+                    final ZonedDateTime zonedDateTime = parseZonedDateTime(text, formatter, zoneIDUTC);
+                    final Calendar instance = GregorianCalendar.getInstance();
+                    instance.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone()));
+                    instance.setTime(Date.from(zonedDateTime.toInstant()));
                     return GregorianCalendar.class.cast(instance);
                 }
             }));
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonJsonb.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonJsonb.java
index fc44eef..62a055a 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonJsonb.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonJsonb.java
@@ -51,9 +51,11 @@ import java.util.OptionalLong;
 // TODO: Optional handling for lists (and arrays)?
 public class JohnzonJsonb implements Jsonb, AutoCloseable, JsonbExtension {
     private final Mapper delegate;
+    private final boolean ijson;
 
-    public JohnzonJsonb(final Mapper build) {
+    public JohnzonJsonb(final Mapper build, final boolean ijson) {
         this.delegate = build;
+        this.ijson = ijson;
     }
 
     @Override
@@ -242,6 +244,8 @@ public class JohnzonJsonb implements Jsonb, AutoCloseable, JsonbExtension {
                 return delegate.writeArrayAsString(toArray(object));
             } else if (Collection.class.isInstance(object)) {
                 return delegate.writeArrayAsString(Collection.class.cast(object));
+            } else if (ijson && isNotObjectOrArray(object)) {
+                throw new JsonbException("I-JSON mode only accepts arrays and objects as root instances");
             }
             return delegate.writeObjectAsString(object);
 
@@ -308,6 +312,8 @@ public class JohnzonJsonb implements Jsonb, AutoCloseable, JsonbExtension {
             return delegate.writeArrayAsString((Object[]) object);
         } else if (isCollection(runtimeType)) {
             return delegate.writeArrayAsString(Collection.class.cast(object));
+        } else if (ijson && isNotObjectOrArray(object)) {
+            throw new JsonbException("I-JSON mode only accepts arrays and objects as root instances");
         }
         return delegate.writeObjectAsString(object);
     }
@@ -324,6 +330,8 @@ public class JohnzonJsonb implements Jsonb, AutoCloseable, JsonbExtension {
             delegate.writeArray((Object[]) object, writer);
         } else if (Collection.class.isInstance(object)) {
             delegate.writeArray(Collection.class.cast(object), writer);
+        } else if (ijson && isNotObjectOrArray(object)) {
+            throw new JsonbException("I-JSON mode only accepts arrays and objects as root instances");
         } else {
             delegate.writeObject(object, writer);
         }
@@ -341,6 +349,8 @@ public class JohnzonJsonb implements Jsonb, AutoCloseable, JsonbExtension {
             delegate.writeArray((Object[]) object, writer);
         } else if (isCollection(runtimeType)) {
             delegate.writeArray(Collection.class.cast(object), writer);
+        } else if (ijson && isNotObjectOrArray(object)) {
+            throw new JsonbException("I-JSON mode only accepts arrays and objects as root instances");
         } else {
             delegate.writeObject(object, writer);
         }
@@ -353,6 +363,8 @@ public class JohnzonJsonb implements Jsonb, AutoCloseable, JsonbExtension {
             delegate.writeArray((Object[]) object, stream);
         } else if (Collection.class.isInstance(object)) {
             delegate.writeArray(Collection.class.cast(object), stream);
+        } else if (ijson && isNotObjectOrArray(object)) {
+            throw new JsonbException("I-JSON mode only accepts arrays and objects as root instances");
         } else {
             delegate.writeObject(object, stream);
         }
@@ -365,11 +377,29 @@ public class JohnzonJsonb implements Jsonb, AutoCloseable, JsonbExtension {
             delegate.writeArray((Object[]) object, stream);
         } else if (isCollection(runtimeType)) {
             delegate.writeArray(Collection.class.cast(object), stream);
+        } else if (ijson && isNotObjectOrArray(object)) {
+            throw new JsonbException("I-JSON mode only accepts arrays and objects as root instances");
         } else {
             delegate.writeObject(object, stream);
         }
     }
 
+    private boolean isNotObjectOrArray(final Object object) {
+        if (String.class.isInstance(object) || Number.class.isInstance(object) || Boolean.class.isInstance(object)) {
+            return true;
+        }
+        if (JsonValue.class.isInstance(object)) {
+            switch (JsonValue.class.cast(object).getValueType()) {
+                case ARRAY:
+                case OBJECT:
+                    return false;
+                default:
+                    return true;
+            }
+        }
+        return false;
+    }
+
     private Object unwrapOptional(final Object inObject) {
         if (Optional.class.isInstance(inObject)) {
             return Optional.class.cast(inObject).orElse(null);
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
index bf34b52..2a396e3 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
@@ -38,6 +38,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.Date;
@@ -368,8 +369,8 @@ public class JsonbAccessMode implements AccessMode, Closeable {
                 converter = new ConverterAdapter<>(new JsonbLocalDateConverter(dateFormat));
             } else if (ZonedDateTime.class == type) {
                 converter = new ConverterAdapter<>(new JsonbZonedDateTimeConverter(dateFormat));
-            } else {
-                throw new IllegalArgumentException(type + " not a supported date type");
+            } else { // can happen if set on the class, todo: refine the checks
+                converter = null; // todo: should we fallback on numberformat?
             }
         } else if (numberFormat != null) {  // TODO: support lists?
             converter = new ConverterAdapter<>(new JsonbNumberConverter(numberFormat));
@@ -611,7 +612,10 @@ public class JsonbAccessMode implements AccessMode, Closeable {
         return Stream.of(genericInterfaces).filter(ParameterizedType.class::isInstance)
                 .filter(i -> Adapter.class.isAssignableFrom(Class.class.cast(ParameterizedType.class.cast(i).getRawType())))
                 .findFirst()
-                .map(pt -> payloadType.isAssignableFrom(Class.class.cast(ParameterizedType.class.cast(pt).getActualTypeArguments()[0])))
+                .map(pt -> {
+                    final Type argument = ParameterizedType.class.cast(pt).getActualTypeArguments()[0];
+                    return Class.class.isInstance(argument) && payloadType.isAssignableFrom(Class.class.cast(argument));
+                })
                 .orElseGet(() -> {
                     final Class<?> superclass = aClass.getSuperclass();
                     return superclass != Object.class && isReversedAdapter(payloadType, superclass, instance);
@@ -666,6 +670,9 @@ public class JsonbAccessMode implements AccessMode, Closeable {
                 }
             }
             keyComparator = (o1, o2) -> {
+                if (o1 != null && o1.equals(o2)) {
+                    return 0;
+                }
                 final int i1 = indexed.indexOf(o1);
                 final int i2 = indexed.indexOf(o2);
                 if (i1 < 0) {
@@ -684,6 +691,9 @@ public class JsonbAccessMode implements AccessMode, Closeable {
                     }
                     return 1;
                 }
+                if (i2 < 0) {
+                    return -1;
+                }
                 return i1 - i2;
             };
         } else if (order != null) {
@@ -731,10 +741,16 @@ public class JsonbAccessMode implements AccessMode, Closeable {
         ReaderConverters(final DecoratedType annotationHolder) {
             final JsonbTypeDeserializer deserializer = annotationHolder.getAnnotation(JsonbTypeDeserializer.class);
             final JsonbTypeAdapter adapter = annotationHolder.getAnnotation(JsonbTypeAdapter.class);
-            final JsonbDateFormat dateFormat = annotationHolder.getAnnotation(JsonbDateFormat.class);
-            final JsonbNumberFormat numberFormat = annotationHolder.getAnnotation(JsonbNumberFormat.class);
+            JsonbDateFormat dateFormat = annotationHolder.getAnnotation(JsonbDateFormat.class);
+            JsonbNumberFormat numberFormat = annotationHolder.getAnnotation(JsonbNumberFormat.class);
             final JohnzonConverter johnzonConverter = annotationHolder.getAnnotation(JohnzonConverter.class);
             validateAnnotations(annotationHolder, adapter, dateFormat, numberFormat, johnzonConverter);
+            if (dateFormat == null && isDateType(annotationHolder.getType())) {
+                dateFormat = annotationHolder.getClassOrPackageAnnotation(JsonbDateFormat.class);
+            }
+            if (numberFormat == null && isNumberType(annotationHolder.getType())) {
+                numberFormat = annotationHolder.getClassOrPackageAnnotation(JsonbNumberFormat.class);
+            }
 
             converter = adapter == null && dateFormat == null && numberFormat == null && johnzonConverter == null ?
                     defaultConverters.get(new AdapterKey(annotationHolder.getType(), String.class)) :
@@ -814,10 +830,16 @@ public class JsonbAccessMode implements AccessMode, Closeable {
         WriterConverters(final DecoratedType initialReader, final Types types) {
             final JsonbTypeSerializer serializer = initialReader.getAnnotation(JsonbTypeSerializer.class);
             final JsonbTypeAdapter adapter = initialReader.getAnnotation(JsonbTypeAdapter.class);
-            final JsonbDateFormat dateFormat = initialReader.getAnnotation(JsonbDateFormat.class);
-            final JsonbNumberFormat numberFormat = initialReader.getAnnotation(JsonbNumberFormat.class);
+            JsonbDateFormat dateFormat = initialReader.getAnnotation(JsonbDateFormat.class);
+            JsonbNumberFormat numberFormat = initialReader.getAnnotation(JsonbNumberFormat.class);
             final JohnzonConverter johnzonConverter = initialReader.getAnnotation(JohnzonConverter.class);
             validateAnnotations(initialReader, adapter, dateFormat, numberFormat, johnzonConverter);
+            if (dateFormat == null && isDateType(initialReader.getType())) {
+                dateFormat = initialReader.getClassOrPackageAnnotation(JsonbDateFormat.class);
+            }
+            if (numberFormat == null && isNumberType(initialReader.getType())) {
+                numberFormat = initialReader.getClassOrPackageAnnotation(JsonbNumberFormat.class);
+            }
 
             converter = adapter == null && dateFormat == null && numberFormat == null && johnzonConverter == null ?
                     defaultConverters.get(new AdapterKey(initialReader.getType(), String.class)) :
@@ -844,6 +866,22 @@ public class JsonbAccessMode implements AccessMode, Closeable {
         }
     }
 
+    private boolean isDateType(final Type type) {
+        if (!Class.class.isInstance(type)) {
+            return false;
+        }
+        final Class<?> clazz = Class.class.cast(type);
+        return type.getTypeName().startsWith("java.time.") || Date.class == type || Calendar.class.isAssignableFrom(clazz);
+    }
+
+    private boolean isNumberType(final Type type) {
+        if (!Class.class.isInstance(type)) {
+            return false;
+        }
+        final Class<?> clazz = Class.class.cast(type);
+        return Number.class.isAssignableFrom(clazz) || clazz.isPrimitive();
+    }
+
     private static class ClassDecoratedType implements DecoratedType {
         private final Class<?> annotations;
 
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java
index 813009c..e800495 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java
@@ -18,24 +18,86 @@
  */
 package org.apache.johnzon.jsonb.converter;
 
-import javax.json.bind.annotation.JsonbDateFormat;
+import java.time.DateTimeException;
 import java.time.Instant;
 import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.util.Date;
 
+import javax.json.bind.annotation.JsonbDateFormat;
+
 public class JsonbDateConverter extends JsonbDateConverterBase<Date> {
+    private static final ZoneId UTC = ZoneId.of("UTC");
+
+    // TODO: cheap impl to avoid to rely on exceptions, better can be to parse format
+    private volatile boolean hasTimezone = true;
+
     public JsonbDateConverter(final JsonbDateFormat dateFormat) {
         super(dateFormat);
     }
 
     @Override
     public String toString(final Date instance) {
-        return formatter == null ? Long.toString(instance.getTime()) : formatter.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(instance.getTime()), ZoneOffset.UTC));
+        return formatter == null ?
+                Long.toString(instance.getTime()) :
+                toStringWithFormatter(instance);
     }
 
     @Override
     public Date fromString(final String text) {
-        return formatter == null ? new Date(Long.parseLong(text)) : Date.from(LocalDateTime.parse(text, formatter).toInstant(ZoneOffset.UTC));
+        return formatter == null ?
+                new Date(Long.parseLong(text)) :
+                fromStringWithFormatter(text);
+    }
+
+    private Date fromStringWithFormatter(final String text) {
+        final boolean hasTimezone = this.hasTimezone;
+        try {
+            if (hasTimezone) {
+                return fromZonedDateTime(text);
+            }
+            return fromLocalDateTime(text);
+        } catch (final DateTimeException dte) {
+            this.hasTimezone = !hasTimezone;
+            if (hasTimezone) {
+                return fromLocalDateTime(text);
+            }
+            return fromZonedDateTime(text);
+        }
+    }
+
+    private String toStringWithFormatter(final Date instance) {
+        final boolean hasTimezone = this.hasTimezone;
+        final Instant instant = Instant.ofEpochMilli(instance.getTime());
+        try {
+            if (hasTimezone) {
+                return toStringFromZonedDateTime(instant);
+            }
+            return toStringFromLocalDateTime(instant);
+        } catch (final DateTimeException dte) {
+            this.hasTimezone = !hasTimezone;
+            if (hasTimezone) {
+                return toStringFromLocalDateTime(instant);
+            }
+            return toStringFromZonedDateTime(instant);
+        }
+    }
+
+    private Date fromLocalDateTime(final String text) {
+        return Date.from(LocalDateTime.parse(text, formatter).toInstant(ZoneOffset.UTC));
+    }
+
+    private Date fromZonedDateTime(final String text) {
+        return Date.from(ZonedDateTime.parse(text, formatter).toInstant());
+    }
+
+    private String toStringFromLocalDateTime(final Instant instant) {
+        return formatter.format(LocalDateTime.ofInstant(instant, UTC));
+    }
+
+    private String toStringFromZonedDateTime(final Instant instant) {
+        return formatter.format(ZonedDateTime.ofInstant(instant, UTC));
     }
 }
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/serializer/JohnzonDeserializationContext.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/serializer/JohnzonDeserializationContext.java
index 65b1595..f64b034 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/serializer/JohnzonDeserializationContext.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/serializer/JohnzonDeserializationContext.java
@@ -30,7 +30,6 @@ import javax.json.stream.JsonParser;
 import javax.json.stream.JsonParsingException;
 
 import org.apache.johnzon.mapper.MappingParser;
-import org.apache.johnzon.mapper.jsonp.RewindableJsonParser;
 
 public class JohnzonDeserializationContext implements DeserializationContext {
     private final MappingParser runtime;
@@ -56,8 +55,8 @@ public class JohnzonDeserializationContext implements DeserializationContext {
     }
 
     private JsonValue read(final JsonParser parser) {
-        final JsonParser.Event next = RewindableJsonParser.class.isInstance(parser) ?
-                RewindableJsonParser.class.cast(parser).getLast() : parser.next();
+        final JsonParser.Event next = /*RewindableJsonParser.class.isInstance(parser) ?
+                RewindableJsonParser.class.cast(parser).getLast() : */ parser.next();
         switch (next) {
             case START_OBJECT:
                 final JsonObjectBuilder objectBuilder = builderFactory.createObjectBuilder();
@@ -67,6 +66,7 @@ public class JohnzonDeserializationContext implements DeserializationContext {
                 final JsonArrayBuilder arrayBuilder = builderFactory.createArrayBuilder();
                 parseArray(parser, arrayBuilder);
                 return arrayBuilder.build();
+            case KEY_NAME:
             case VALUE_STRING:
                 return jsonp.createValue(parser.getString());
             case VALUE_FALSE:
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DateFormatTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DateFormatTest.java
new file mode 100644
index 0000000..14709ea
--- /dev/null
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DateFormatTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.johnzon.jsonb;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.json.bind.JsonbConfig;
+import javax.json.bind.annotation.JsonbDateFormat;
+
+import org.apache.johnzon.jsonb.model.Holder;
+import org.apache.johnzon.jsonb.model.packageformat.FormatOnClassModel;
+import org.apache.johnzon.jsonb.test.JsonbRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class DateFormatTest {
+    @Rule
+    public final JsonbRule jsonb = new JsonbRule();
+
+    @Test
+    public void dateFormatMethods() {
+        final Date instance = new Date(0);
+        {
+            final String json = jsonb.toJson(new DateHolder() {{ setInstance(instance); }});
+            final String expected = "{\"instance\":\"" +
+                DateTimeFormatter.ofPattern("E DD MMM yyyy HH:mm:ss z")
+                        .withLocale(new Locale.Builder().setLanguage("it").build())
+                        .format(ZonedDateTime.ofInstant(instance.toInstant(), ZoneId.of("UTC"))) + "\"}";
+            assertEquals(expected, json);
+        }
+        {
+            final Calendar c = Calendar.getInstance(Locale.GERMAN);
+            c.set(Calendar.YEAR, Integer.parseInt("19700001".substring(0, 4)));
+            c.set(Calendar.MONTH, Integer.parseInt("19700001".substring(4, 6)));
+            c.set(Calendar.DATE, Integer.parseInt("19700001".substring(6, 8)));
+            final String json = String.format("{\"instance\":\"%s 1970 01:00:00 MEZ\"}",
+                    new SimpleDateFormat("EEE' 'dd' 'MMM", Locale.GERMAN).format(c.getTime()));
+            final DateHolder holder = jsonb.fromJson(json, DateHolder.class);
+            assertEquals(instance, holder.getInstance());
+        }
+    }
+
+    @Test
+    public void packageConfigOverridenByClass() throws Exception {
+        try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+                .withDateFormat("E DD MMM yyyy HH:mm:ss z", Locale.CANADA))) {
+
+            final Date instance = new Date(0);
+            final Locale locale = new Locale.Builder().setLanguage("de").build();
+
+            {
+                final String json = jsonb.toJson(new FormatOnClassModel() {{ setInstance(instance); }});
+                final String expected = "{\"instance\":\""
+                        + DateTimeFormatter.ofPattern("E DD MMM yyyy HH:mm:ss")
+                                .withLocale(locale)
+                                .format(ZonedDateTime.ofInstant(instance.toInstant(), ZoneId.of("UTC")))
+                        +  "\"}";
+                assertEquals(expected, json);
+            }
+        }
+    }
+
+    public static class DateHolder implements Holder<Date> {
+        private Date instance;
+
+        @Override
+        @JsonbDateFormat(value = "E DD MMM yyyy HH:mm:ss z", locale = "it")
+        public Date getInstance() {
+            return instance;
+        }
+
+        @Override
+        @JsonbDateFormat(value = "E DD MMM yyyy HH:mm:ss z", locale = "de")
+        public void setInstance(Date instance) {
+            this.instance = instance;
+        }
+    }
+}
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/IJsonTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/IJsonTest.java
new file mode 100644
index 0000000..6d709fe
--- /dev/null
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/IJsonTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.johnzon.jsonb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.json.bind.JsonbConfig;
+import javax.json.bind.JsonbException;
+
+import org.apache.johnzon.jsonb.model.Holder;
+import org.junit.Test;
+
+public class IJsonTest {
+    @Test
+    public void binary() throws Exception {
+        final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withStrictIJSON(true));
+        final String jsonString = jsonb.toJson(new Bytes());
+        assertEquals("{\"data\":\"VGVzdCBTdHJpbmc=\"}", jsonString);
+        jsonb.close();
+    }
+
+    @Test
+    public void date() throws Exception {
+        final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withStrictIJSON(true));
+        final Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(1970, 0, 1);
+        cal.setTimeZone(TimeZone.getTimeZone("UTC"));
+        final String json = jsonb.toJson(new DateHolder() {{ setInstance(cal.getTime()); }});
+        assertEquals("{\"instance\":\"1970-01-01T00:00:00Z+00:00\"}", json);
+        jsonb.close();
+    }
+
+    @Test
+    public void calendar() throws Exception {
+        final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withStrictIJSON(true));
+
+        final Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(1970, Calendar.JANUARY, 1);
+        cal.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
+
+        final String json = jsonb.toJson(new CalendarHolder() {{ setInstance(cal); }});
+        assertEquals("{\"instance\":\"1970-01-01T00:00:00Z+01:00\"}", json);
+        jsonb.close();
+    }
+
+    @Test
+    public void onlyObjectAndArrayCanBeRoot() throws Exception {
+        try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withStrictIJSON(true))) {
+            try {
+                jsonb.toJson("Test String");
+                fail();
+            } catch (final JsonbException e) {
+                // ok
+            }
+        }
+    }
+
+    public class DateHolder implements Holder<Date> {
+        private Date instance;
+
+        @Override
+        public Date getInstance() {
+            return instance;
+        }
+
+        @Override
+        public void setInstance(final Date instance) {
+            this.instance = instance;
+        }
+    }
+
+    public class CalendarHolder implements Holder<Calendar> {
+        private Calendar instance;
+
+        @Override
+        public Calendar getInstance() {
+            return instance;
+        }
+
+        @Override
+        public void setInstance(final Calendar instance) {
+            this.instance = instance;
+        }
+    }
+
+    public static class Bytes {
+        private byte[] data = "Test String".getBytes();
+
+        public byte[] getData() {
+            return data;
+        }
+
+        public void setData(final byte[] data) {
+            this.data = data;
+        }
+    }
+}
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/NumberFormatTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/NumberFormatTest.java
new file mode 100644
index 0000000..324aedb
--- /dev/null
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/NumberFormatTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.johnzon.jsonb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.johnzon.jsonb.model.packageformat.FormatFromPackageModel;
+import org.apache.johnzon.jsonb.model.packageformat.FormatFromClassModel;
+import org.apache.johnzon.jsonb.test.JsonbRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class NumberFormatTest {
+    @Rule
+    public final JsonbRule jsonb = new JsonbRule();
+
+    @Test
+    public void packageFormat() {
+        final String json = jsonb.toJson(new FormatFromPackageModel() {{ setInstance(123456.789); }});
+        assertTrue(json, json.matches("\\{\\s*\"instance\"\\s*:\\s*\"123.456,8\"\\s*}"));
+
+        final FormatFromPackageModel unmarshalledObject = jsonb.fromJson(
+                "{ \"instance\" : \"123.456,789\" }", FormatFromPackageModel.class);
+        assertEquals(123456.789, unmarshalledObject.getInstance(), 0);
+    }
+
+    @Test
+    public void formatType() {
+        final String json = jsonb.toJson(new FormatFromClassModel() {{ setInstance(123456.789); }});
+        assertTrue(json, json.matches("\\{\\s*\"instance\"\\s*:\\s*\"123.456,8\"\\s*}"));
+
+        final FormatFromPackageModel unmarshalledObject = jsonb.fromJson(
+                "{ \"instance\" : \"123.456,789\" }", FormatFromClassModel.class);
+        assertEquals(123456.789, unmarshalledObject.getInstance(), 0);
+    }
+}
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/OrderTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/OrderTest.java
new file mode 100644
index 0000000..631a070
--- /dev/null
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/OrderTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.johnzon.jsonb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Type;
+
+import javax.json.bind.annotation.JsonbPropertyOrder;
+import javax.json.bind.annotation.JsonbTypeDeserializer;
+import javax.json.bind.annotation.JsonbTypeSerializer;
+import javax.json.bind.serializer.DeserializationContext;
+import javax.json.bind.serializer.JsonbDeserializer;
+import javax.json.bind.serializer.JsonbSerializer;
+import javax.json.bind.serializer.SerializationContext;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonParser;
+
+import org.apache.johnzon.jsonb.model.Holder;
+import org.apache.johnzon.jsonb.test.JsonbRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class OrderTest {
+    @Rule
+    public final JsonbRule jsonb = new JsonbRule();
+
+    @Test
+    public void partial() {
+        final String jsonb = this.jsonb.toJson(new PartialModel());
+        assertTrue(jsonb, jsonb.matches(
+                "\\{\\s*\"third\"\\s*:\\s*\"Third\"\\s*,\\s*\"fourth\"\\s*:\\s*\"Fourth\".*}"));
+    }
+
+    @Test
+    public void typeSerializer() {
+        final HolderHolder container = new HolderHolder();
+        final StringHolder instance = new StringHolder();
+        instance.setInstance("Test String");
+        container.setInstance(instance);
+
+        final String json = jsonb.toJson(container);
+        assertTrue(json, json.matches(
+                "\\{\\s*\"instance\"\\s*:\\s*\\{\\s*\"instance\"\\s*:\\s*\"Test String Serialized\"\\s*}\\s*}"));
+
+        final HolderHolder unmarshalledObject = jsonb.fromJson("{ \"instance\" : { \"instance\" : \"Test String\" } }", HolderHolder.class);
+        assertEquals("Test String Deserialized", unmarshalledObject.getInstance().getInstance());
+    }
+
+    public static class StringHolder implements Holder<String> {
+        private String instance = "Test";
+
+        public String getInstance() {
+            return instance;
+        }
+
+        public void setInstance(final String instance) {
+            this.instance = instance;
+        }
+    }
+
+    public static class SimpleContainerDeserializer implements JsonbDeserializer<StringHolder> {
+        @Override
+        public StringHolder deserialize(final JsonParser parser, final DeserializationContext ctx, final Type type) {
+            final StringHolder container = new StringHolder();
+
+            while (parser.hasNext()) {
+                final JsonParser.Event event = parser.next();
+                if (event == JsonParser.Event.START_OBJECT) {
+                    continue;
+                }
+                if (event == JsonParser.Event.END_OBJECT) {
+                    break;
+                }
+                if (event == JsonParser.Event.KEY_NAME && "instance".equals(parser.getString())) {
+                    container.setInstance(ctx.deserialize(String.class, parser) + " Deserialized");
+                }
+            }
+
+            return container;
+        }
+    }
+
+    public static class SimpleContainerSerializer implements JsonbSerializer<StringHolder> {
+        @Override
+        public void serialize(final StringHolder container, final JsonGenerator generator,
+                              final SerializationContext ctx) {
+            generator.writeStartObject();
+            ctx.serialize("instance", container.getInstance() + " Serialized", generator);
+            generator.writeEnd();
+        }
+    }
+
+    public static class HolderHolder implements Holder<StringHolder> {
+        @JsonbTypeSerializer(SimpleContainerSerializer.class)
+        @JsonbTypeDeserializer(SimpleContainerDeserializer.class)
+        private StringHolder instance;
+
+        @Override
+        public StringHolder getInstance() {
+            return instance;
+        }
+
+        @Override
+        public void setInstance(StringHolder instance) {
+            this.instance = instance;
+        }
+    }
+
+    @JsonbPropertyOrder({ "third", "fourth" })
+    public class PartialModel {
+        private String first = "First";
+
+        private String second = "Second";
+
+        private String third = "Third";
+
+        private String fourth = "Fourth";
+
+        private String anyOther = "Fifth String property starting with A";
+
+        public String getThird() {
+            return third;
+        }
+
+        public void setThird(final String third) {
+            this.third = third;
+        }
+
+        public String getFourth() {
+            return fourth;
+        }
+
+        public void setFourth(final String fourth) {
+            this.fourth = fourth;
+        }
+
+        public String getAnyOther() {
+            return anyOther;
+        }
+
+        public void setAnyOther(final String anyOther) {
+            this.anyOther = anyOther;
+        }
+
+        public String getFirst() {
+            return first;
+        }
+
+        public void setFirst(final String first) {
+            this.first = first;
+        }
+
+        public String getSecond() {
+            return second;
+        }
+
+        public void setSecond(final String second) {
+            this.second = second;
+        }
+    }
+}
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/Holder.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/Holder.java
new file mode 100644
index 0000000..e0302de
--- /dev/null
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/Holder.java
@@ -0,0 +1,24 @@
+/*
+ * 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.johnzon.jsonb.model;
+
+public interface Holder<T> {
+    T getInstance();
+    void setInstance(T instance);
+}
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/FormatFromClassModel.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/FormatFromClassModel.java
new file mode 100644
index 0000000..e6651e5
--- /dev/null
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/FormatFromClassModel.java
@@ -0,0 +1,25 @@
+/*
+ * 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.johnzon.jsonb.model.packageformat;
+
+import javax.json.bind.annotation.JsonbNumberFormat;
+
+@JsonbNumberFormat("###,###.###")
+public class FormatFromClassModel extends FormatFromPackageModel {
+}
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/FormatFromPackageModel.java
similarity index 51%
copy from johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java
copy to johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/FormatFromPackageModel.java
index 813009c..0301c5c 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/FormatFromPackageModel.java
@@ -16,26 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.johnzon.jsonb.converter;
+package org.apache.johnzon.jsonb.model.packageformat;
 
-import javax.json.bind.annotation.JsonbDateFormat;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.util.Date;
+import org.apache.johnzon.jsonb.model.Holder;
 
-public class JsonbDateConverter extends JsonbDateConverterBase<Date> {
-    public JsonbDateConverter(final JsonbDateFormat dateFormat) {
-        super(dateFormat);
-    }
+public class FormatFromPackageModel implements Holder<Double> {
+    private Double instance;
 
     @Override
-    public String toString(final Date instance) {
-        return formatter == null ? Long.toString(instance.getTime()) : formatter.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(instance.getTime()), ZoneOffset.UTC));
+    public Double getInstance() {
+        return instance;
     }
 
     @Override
-    public Date fromString(final String text) {
-        return formatter == null ? new Date(Long.parseLong(text)) : Date.from(LocalDateTime.parse(text, formatter).toInstant(ZoneOffset.UTC));
+    public void setInstance(Double instance) {
+        this.instance = instance;
     }
 }
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/FormatOnClassModel.java
similarity index 56%
copy from johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java
copy to johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/FormatOnClassModel.java
index 813009c..24162f7 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/FormatOnClassModel.java
@@ -16,26 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.johnzon.jsonb.converter;
+package org.apache.johnzon.jsonb.model.packageformat;
 
-import javax.json.bind.annotation.JsonbDateFormat;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
 import java.util.Date;
 
-public class JsonbDateConverter extends JsonbDateConverterBase<Date> {
-    public JsonbDateConverter(final JsonbDateFormat dateFormat) {
-        super(dateFormat);
-    }
+import javax.json.bind.annotation.JsonbDateFormat;
+
+import org.apache.johnzon.jsonb.model.Holder;
+
+@JsonbDateFormat(value = "E DD MMM yyyy HH:mm:ss", locale = "de")
+public class FormatOnClassModel implements Holder<Date> {
+    private Date instance;
 
     @Override
-    public String toString(final Date instance) {
-        return formatter == null ? Long.toString(instance.getTime()) : formatter.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(instance.getTime()), ZoneOffset.UTC));
+    public Date getInstance() {
+        return instance;
     }
 
     @Override
-    public Date fromString(final String text) {
-        return formatter == null ? new Date(Long.parseLong(text)) : Date.from(LocalDateTime.parse(text, formatter).toInstant(ZoneOffset.UTC));
+    public void setInstance(final Date instance) {
+        this.instance = instance;
     }
 }
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/package-info.java
similarity index 52%
copy from johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java
copy to johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/package-info.java
index 813009c..569da0e 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbDateConverter.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/model/packageformat/package-info.java
@@ -16,26 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.johnzon.jsonb.converter;
+@JsonbNumberFormat(value = "###,###.#", locale = "de")
+@JsonbDateFormat(value = "E DD MMM yyyy HH:mm:ss z", locale = "it")
+package org.apache.johnzon.jsonb.model.packageformat;
 
 import javax.json.bind.annotation.JsonbDateFormat;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.util.Date;
-
-public class JsonbDateConverter extends JsonbDateConverterBase<Date> {
-    public JsonbDateConverter(final JsonbDateFormat dateFormat) {
-        super(dateFormat);
-    }
-
-    @Override
-    public String toString(final Date instance) {
-        return formatter == null ? Long.toString(instance.getTime()) : formatter.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(instance.getTime()), ZoneOffset.UTC));
-    }
-
-    @Override
-    public Date fromString(final String text) {
-        return formatter == null ? new Date(Long.parseLong(text)) : Date.from(LocalDateTime.parse(text, formatter).toInstant(ZoneOffset.UTC));
-    }
-}
+import javax.json.bind.annotation.JsonbNumberFormat;
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java
index 86a80b6..5e7c5c3 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/DynamicMappingGenerator.java
@@ -100,7 +100,8 @@ public class DynamicMappingGenerator implements MappingGenerator {
 
         @Override
         public JsonGenerator writeStartObject() {
-            return delegate.writeStartObject();
+            // return delegate.writeStartObject();
+            return this;
         }
 
         @Override
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java
index 831f895..95a9780 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java
@@ -109,9 +109,7 @@ public class FieldAccessMode extends BaseAccessMode {
 
         @Override
         public <T extends Annotation> T getClassOrPackageAnnotation(final Class<T> clazz) {
-            final Class<?> declaringClass = field.getDeclaringClass();
-            final T annotation = Meta.getAnnotation(declaringClass, clazz);
-            return annotation == null ? Meta.getAnnotation(declaringClass.getPackage(), clazz) : annotation;
+            return Meta.getClassOrPackageAnnotation(field, clazz);
         }
 
         @Override
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/Meta.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/Meta.java
index 9e57c09..dbf8e00 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/Meta.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/Meta.java
@@ -18,28 +18,66 @@
  */
 package org.apache.johnzon.mapper.access;
 
+import static java.util.Arrays.asList;
+
 import java.lang.annotation.Annotation;
 import java.lang.reflect.AccessibleObject;
-import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.HashMap;
 import java.util.Map;
-
-import static java.util.Arrays.asList;
+import java.util.function.Supplier;
 
 public final class Meta {
     private Meta() {
         // no-op
     }
 
-    public static <T extends Annotation> T getAnnotation(final AccessibleObject holder, final Class<T> api) {
+    public static <T extends Annotation> T getAnnotation(final Method holder, final Class<T> api) {
+        return getDirectAnnotation(holder, api);
+    }
+
+    public static <T extends Annotation> T getAnnotation(final Field holder, final Class<T> api) {
+        return getDirectAnnotation(holder, api);
+    }
+
+    public static <T extends Annotation> T getClassOrPackageAnnotation(final Method holder, final Class<T> api) {
+        return getIndirectAnnotation(api, holder::getDeclaringClass, () -> holder.getDeclaringClass().getPackage());
+    }
+
+    public static <T extends Annotation> T getClassOrPackageAnnotation(final Field holder, final Class<T> api) {
+        return getIndirectAnnotation(api, holder::getDeclaringClass, () -> holder.getDeclaringClass().getPackage());
+    }
+
+    private static <T extends Annotation> T getDirectAnnotation(final AccessibleObject holder, final Class<T> api) {
         final T annotation = holder.getAnnotation(api);
         if (annotation != null) {
             return annotation;
         }
-        return findMeta(holder.getAnnotations(), api);
+        final T meta = findMeta(holder.getAnnotations(), api);
+        if (meta != null) {
+            return meta;
+        }
+        return null;
+    }
+
+    private static <T extends Annotation> T getIndirectAnnotation(final Class<T> api,
+                                                                  final Supplier<Class<?>> ownerSupplier,
+                                                                  final Supplier<Package> packageSupplier) {
+        final T ownerAnnotation = ownerSupplier.get().getAnnotation(api);
+        if (ownerAnnotation != null) {
+            return ownerAnnotation;
+        } // todo: meta?
+        final Package pck = packageSupplier.get();
+        if (pck != null) {
+            final T pckAnnotation = pck.getAnnotation(api);
+            if (pckAnnotation != null) {
+                return pckAnnotation;
+            }
+        } // todo: meta?
+        return null;
     }
 
     public static <T extends Annotation> T getAnnotation(final Class<?> clazz, final Class<T> api) {
@@ -81,18 +119,15 @@ public final class Meta {
 
     private static <T extends Annotation> T newAnnotation(final Map<String, Method> methodMapping, final Annotation user, final T johnzon) {
         return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{johnzon.annotationType()},
-                new InvocationHandler() {
-                    @Override
-                    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
-                        final Method m = methodMapping.get(method.getName());
-                        try {
-                            if (m.getDeclaringClass() == user.annotationType()) {
-                                return m.invoke(user, args);
-                            }
-                            return m.invoke(johnzon, args);
-                        } catch (final InvocationTargetException ite) {
-                            throw ite.getTargetException();
+                (proxy, method, args) -> {
+                    final Method m = methodMapping.get(method.getName());
+                    try {
+                        if (m.getDeclaringClass() == user.annotationType()) {
+                            return m.invoke(user, args);
                         }
+                        return m.invoke(johnzon, args);
+                    } catch (final InvocationTargetException ite) {
+                        throw ite.getTargetException();
                     }
                 });
     }
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java
index b694bd2..959d69f 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java
@@ -116,9 +116,7 @@ public class MethodAccessMode extends BaseAccessMode {
 
         @Override
         public <T extends Annotation> T getClassOrPackageAnnotation(final Class<T> clazz) {
-            final Class<?> declaringClass = method.getDeclaringClass();
-            final T annotation = Meta.getAnnotation(declaringClass, clazz);
-            return annotation == null ? Meta.getAnnotation(declaringClass.getPackage(), clazz) : annotation;
+            return Meta.getClassOrPackageAnnotation(method, clazz);
         }
 
         @Override