add native support for Optional in Mapper
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JsonbTypesTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JsonbTypesTest.java
index 2c5987b..04d4370 100644
--- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JsonbTypesTest.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JsonbTypesTest.java
@@ -19,6 +19,7 @@
 package org.apache.johnzon.jsonb;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 import java.io.StringReader;
 import java.math.BigDecimal;
@@ -54,6 +55,8 @@
 
 import jakarta.json.bind.Jsonb;
 import jakarta.json.bind.JsonbConfig;
+import jakarta.json.bind.annotation.JsonbCreator;
+import jakarta.json.bind.annotation.JsonbProperty;
 import jakarta.json.bind.spi.JsonbProvider;
 
 import org.apache.cxf.common.util.StringUtils;
@@ -131,7 +134,37 @@
         readAndWriteWithDateFormat(DateTimeFormatter.ofPattern("yyyyMMdd+HHmmssZ"), "yyyyMMdd+HHmmssZ");
         readAndWriteWithDateFormat(DateTimeFormatter.ofPattern("yyyy-MM-dd"), "yyyy-MM-dd");
     }
-    
+
+    @Test
+    public void testOptionalViaJsonbCreatorFullJson() {
+        final Jsonb jsonb = newJsonb();
+        final String json = "{\"intOptional\":4711,\"stringOptional\":\"testVal\"}";
+        final OptionalTypes optionalTypes = jsonb.fromJson(json, OptionalTypes.class);
+        assertNotNull(optionalTypes);
+        assertEquals("testVal", optionalTypes.getOptionalString());
+        assertEquals(4711, optionalTypes.getOptionalInt());
+    }
+
+    @Test
+    public void testOptionalViaJsonbCreatorEmptyJson() {
+        final Jsonb jsonb = newJsonb();
+        final String json = "{ }";
+        final OptionalTypes optionalTypes = jsonb.fromJson(json, OptionalTypes.class);
+        assertNotNull(optionalTypes);
+        assertEquals(OptionalTypes.EMPTY, optionalTypes.getOptionalString());
+        assertEquals(-1, optionalTypes.getOptionalInt());
+    }
+
+    @Test
+    public void testOptionalViaJsonbCreatorPartialJson() {
+        final Jsonb jsonb = newJsonb();
+        final String json = "{\"intOptional\":4711}";
+        final OptionalTypes optionalTypes = jsonb.fromJson(json, OptionalTypes.class);
+        assertNotNull(optionalTypes);
+        assertEquals(OptionalTypes.EMPTY, optionalTypes.getOptionalString());
+        assertEquals(4711, optionalTypes.getOptionalInt());
+    }
+
     private void readAndWriteWithDateFormat(DateTimeFormatter dateTimeFormatter, String dateFormat) throws Exception {
         final LocalDate localDate = LocalDate.of(2015, 1, 1);
         final LocalDateTime localDateTime = LocalDateTime.of(2015, 1, 1, 1, 1);
@@ -520,4 +553,31 @@
                 date, calendar, gregorianCalendar, instant, localDateTime, localDate, offsetDateTime);
         }
     }
+
+    public static class OptionalTypes {
+        public final static String EMPTY = "    ";
+        private final String optionalString;
+        private final int optionalInt;
+
+        public OptionalTypes(String optionalString, int optionalInt) {
+            this.optionalString = optionalString;
+            this.optionalInt = optionalInt;
+        }
+
+        @JsonbCreator
+        public static OptionalTypes init(@JsonbProperty("stringOptional") Optional<String> stringOptional,
+                                         @JsonbProperty("intOptional") OptionalInt intOptional) {
+            return new OptionalTypes(
+                    stringOptional.orElse(EMPTY),
+                    intOptional.orElse(-1));
+        }
+
+        public String getOptionalString() {
+            return optionalString;
+        }
+
+        public int getOptionalInt() {
+            return optionalInt;
+        }
+    }
 }
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
index de9c1d3..61dd5c9 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
@@ -42,30 +42,7 @@
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.math.BigInteger;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.NavigableMap;
-import java.util.NavigableSet;
-import java.util.PriorityQueue;
-import java.util.Queue;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import java.util.TreeMap;
-import java.util.TreeSet;
+import java.util.*;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -621,6 +598,19 @@
                             final Type type, final Adapter itemConverter, final JsonPointerTracker jsonPointer,
                             final Type rootType) {
         if (jsonValue == null) {
+            if (OptionalInt.class == type) {
+                return OptionalInt.empty();
+            }
+            if (OptionalDouble.class == type) {
+                return OptionalDouble.empty();
+            }
+            if (OptionalLong.class == type) {
+                return OptionalLong.empty();
+            }
+            if (type instanceof ParameterizedType && Optional.class == ((ParameterizedType)type).getRawType()) {
+                return Optional.empty();
+            }
+
             return null;
         }
 
@@ -693,6 +683,9 @@
             if (type == Long.class || type == long.class) {
                 return number.longValueExact();
             }
+            if (type == OptionalLong.class) {
+                return OptionalLong.of(number.longValueExact());
+            }
 
             if (type == Float.class || type == float.class) {
                 return (float) number.doubleValue();
@@ -701,6 +694,9 @@
             if (type == Double.class || type == double.class) {
                 return number.doubleValue();
             }
+            if (type == OptionalDouble.class) {
+                return OptionalDouble.of(number.doubleValue());
+            }
 
             if (type == BigInteger.class) {
                 return number.bigIntegerValue();
@@ -713,6 +709,9 @@
             if (type == Integer.class || type == int.class) {
                 return number.intValueExact();
             }
+            if (type == OptionalInt.class) {
+                return OptionalInt.of(number.intValueExact());
+            }
 
             if (type == Short.class || type == short.class) {
                 final int intValue = number.intValue();
@@ -1167,7 +1166,15 @@
         }
         if (converter == null) {
             if (ParameterizedType.class.isInstance(aClass)) {
-                return convertTo(ParameterizedType.class.cast(aClass).getRawType(), text);
+                ParameterizedType parameterizedType = (ParameterizedType) aClass;
+                final Type rawType = parameterizedType.getRawType();
+                final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
+                if (Optional.class == rawType && actualTypeArguments.length == 1) {
+                    // convert the type parameter
+                    final Type actualType = actualTypeArguments[0];
+                    return Optional.of(convertTo(actualType, text));
+                }
+                return convertTo(rawType, text);
             }
             throw new MapperException("Missing a Converter for type " + aClass + " to convert the JSON String '" +
                     text + "' . Please register a custom converter for it.");