From 54aab3a7029f0a2475bf74333789e16abf50653d Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Mon, 30 May 2022 17:39:40 +0800 Subject: [PATCH] refactor use ObjectReaderImplValue --- .../reader/ObjectReaderBaseModule.java | 18 ++- .../fastjson2/reader/ObjectReaderCreator.java | 93 ++++++++++++++++ .../reader/ObjectReaderImplValue.java | 103 ++++++++++++++++++ .../fastjson2/reader/ObjectReaderMisc.java | 15 --- .../support/money/CurrencyUnitReader.java | 36 ------ .../fastjson2/support/money/MoneySupport.java | 50 +++++++-- .../support/money/NumberValueReader.java | 38 ------- .../annotation/JSONFieldValueTest.java | 85 +++++++++++++++ .../fastjson2/date/DateFormatTest.java | 10 ++ 9 files changed, 350 insertions(+), 98 deletions(-) create mode 100644 core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderImplValue.java delete mode 100644 core/src/main/java/com/alibaba/fastjson2/support/money/CurrencyUnitReader.java delete mode 100644 core/src/main/java/com/alibaba/fastjson2/support/money/NumberValueReader.java create mode 100644 core/src/test/java/com/alibaba/fastjson2/annotation/JSONFieldValueTest.java diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java index 6741372f89..c361ea3f2b 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java @@ -24,6 +24,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.net.*; +import java.text.SimpleDateFormat; import java.time.*; import java.time.format.DateTimeFormatter; import java.util.*; @@ -879,6 +880,11 @@ private void getFieldInfo(FieldInfo fieldInfo, JSONField jsonField) { fieldInfo.ordinal = ordinal; } + boolean value = jsonField.value(); + if (value) { + fieldInfo.features |= FieldInfo.VALUE_MASK; + } + if (jsonField.unwrapped()) { fieldInfo.features |= FieldInfo.UNWRAPPED_MASK; } @@ -1643,10 +1649,18 @@ public ObjectReader getObjectReader(ObjectReaderProvider provider, Type type) { return MoneySupport.createMonetaryAmountReader(); case "javax.money.NumberValue": return MoneySupport.createNumberValueReader(); - case "java.net.InetAddress": case "java.net.InetSocketAddress": - case "java.text.SimpleDateFormat": return new ObjectReaderMisc((Class) type); + case "java.net.InetAddress": + return ObjectReaderImplValue.of((Class) type, String.class, address -> { + try { + return InetAddress.getByName(address); + } catch (UnknownHostException e) { + throw new JSONException("create address error", e); + } + }); + case "java.text.SimpleDateFormat": + return ObjectReaderImplValue.of((Class) type, String.class, SimpleDateFormat::new); case "java.lang.Throwable": case "java.lang.Exception": case "java.lang.IllegalStateException": diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreator.java b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreator.java index d33d03ebb9..05b775df8b 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreator.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderCreator.java @@ -311,6 +311,10 @@ protected ObjectReader createObjectReaderWithCreator( } } + if (parameters.length == 1 && (fieldInfo.features & FieldInfo.VALUE_MASK) != 0) { + break; + } + String fieldName = fieldInfo.fieldName; if (fieldName == null || fieldName.isEmpty()) { if (beanInfo.createParameterNames != null && i < beanInfo.createParameterNames.length) { @@ -366,6 +370,48 @@ protected ObjectReader createObjectReaderWithCreator( } } + if (parameters.length == 1 && (fieldInfo.features & FieldInfo.VALUE_MASK) != 0) { + Type valueType = beanInfo.creatorConstructor == null + ? beanInfo.createMethod.getGenericParameterTypes()[0] + : beanInfo.creatorConstructor.getGenericParameterTypes()[0]; + Class valueClass = beanInfo.creatorConstructor == null + ? beanInfo.createMethod.getParameterTypes()[0] + : beanInfo.creatorConstructor.getParameterTypes()[0]; + + JSONSchema jsonSchema = null; + if (fieldInfo.schema != null && !fieldInfo.schema.isEmpty()) { + JSONObject object = JSON.parseObject(fieldInfo.schema); + if (!object.isEmpty()) { + jsonSchema = JSONSchema.of(object, valueClass); + } + } + + Object defaultValue = fieldInfo.defaultValue; + if (defaultValue != null && defaultValue.getClass() != valueClass) { + Function typeConvert = JSONFactory + .getDefaultObjectReaderProvider() + .getTypeConvert(defaultValue.getClass(), valueType); + if (typeConvert != null) { + defaultValue = typeConvert.apply(defaultValue); + } else { + throw new JSONException("illegal defaultValue : " + defaultValue + ", class " + valueClass.getName()); + } + } + + return new ObjectReaderImplValue( + objectClass, + valueType, + valueClass, + fieldInfo.features, + fieldInfo.format, + defaultValue, + jsonSchema, + beanInfo.creatorConstructor, + beanInfo.createMethod, + null + ); + } + Function, Object> function; if (beanInfo.creatorConstructor != null) { function = createFunction(beanInfo.creatorConstructor, beanInfo.markerConstructor, paramNames); @@ -662,6 +708,53 @@ public ObjectReader createObjectReader(Class objectClass, Type objectT && !(Throwable.class.isAssignableFrom(objectClass)) && defaultConstructor == null && matchCount != parameterNames.length) { + if (creatorConstructor.getParameterCount() == 1) { + FieldInfo fieldInfo = new FieldInfo(); + for (ObjectReaderModule module : modules) { + ObjectReaderAnnotationProcessor annotationProcessor = module.getAnnotationProcessor(); + if (annotationProcessor != null) { + annotationProcessor.getFieldInfo(fieldInfo, objectClass, creatorConstructor, 0, creatorConstructor.getParameters()[0]); + } + } + if ((fieldInfo.features & FieldInfo.VALUE_MASK) != 0) { + Type valueType = creatorConstructor.getGenericParameterTypes()[0]; + Class valueClass = creatorConstructor.getParameterTypes()[0]; + + JSONSchema jsonSchema = null; + if (fieldInfo.schema != null && !fieldInfo.schema.isEmpty()) { + JSONObject object = JSON.parseObject(fieldInfo.schema); + if (!object.isEmpty()) { + jsonSchema = JSONSchema.of(object, valueClass); + } + } + + Object defaultValue = fieldInfo.defaultValue; + if (defaultValue != null && defaultValue.getClass() != valueClass) { + Function typeConvert = JSONFactory + .getDefaultObjectReaderProvider() + .getTypeConvert(defaultValue.getClass(), valueType); + if (typeConvert != null) { + defaultValue = typeConvert.apply(defaultValue); + } else { + throw new JSONException("illegal defaultValue : " + defaultValue + ", class " + valueClass.getName()); + } + } + + return new ObjectReaderImplValue( + objectClass, + valueType, + valueClass, + fieldInfo.features, + fieldInfo.format, + defaultValue, + jsonSchema, + creatorConstructor, + null, + null + ); + } + } + Function, T> function = new ConstructorFunction(alternateConstructors, creatorConstructor, parameterNames); FieldReader[] paramFieldReaders = createFieldReaders(creatorConstructor.getParameters(), parameterNames); return new ObjectReaderNoneDefaultConstrutor( diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderImplValue.java b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderImplValue.java new file mode 100644 index 0000000000..60f427ecc3 --- /dev/null +++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderImplValue.java @@ -0,0 +1,103 @@ +package com.alibaba.fastjson2.reader; + +import com.alibaba.fastjson2.JSONException; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.schema.JSONSchema; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.function.Function; + +public class ObjectReaderImplValue + implements ObjectReader { + final Type valueType; + final Class valueClass; + final long features; + final Constructor constructor; + final Method factoryMethod; + final Function function; + final JSONSchema schema; + final Object emptyVariantArgs; + ObjectReader valueReader; + + public ObjectReaderImplValue( + Class objectClass, + Type valueType, + Class valueClass, + long features, + String format, + Object defaultValue, + JSONSchema schema, + Constructor constructor, + Method factoryMethod, + Function function + ) { + this.valueType = valueType; + this.valueClass = valueClass; + this.features = features; + this.schema = schema; + this.constructor = constructor; + this.factoryMethod = factoryMethod; + this.function = function; + + if (factoryMethod != null && factoryMethod.getParameterCount() == 2) { + Class varArgType = factoryMethod.getParameterTypes()[1].getComponentType(); + emptyVariantArgs = Array.newInstance(varArgType, 0); + } else { + emptyVariantArgs = null; + } + } + + @Override + public T readObject(JSONReader jsonReader, long features) { + if (valueReader == null) { + valueReader = jsonReader.getObjectReader(valueType); + } + + I value = (I) valueReader.readObject(jsonReader, features | this.features); + + if (schema != null) { + schema.validate(value); + } + + T object; + + if (function != null) { + try { + object = function.apply(value); + } catch (Exception ex) { + throw new JSONException("create object error", ex); + } + } else if (constructor != null) { + try { + object = constructor.newInstance(value); + } catch (Exception ex) { + throw new JSONException("create object error", ex); + } + } else if (factoryMethod != null) { + try { + if (emptyVariantArgs != null) { + object = (T) factoryMethod.invoke(null, value, emptyVariantArgs); + } else { + object = (T) factoryMethod.invoke(null, value); + } + } catch (Exception ex) { + throw new JSONException("create object error", ex); + } + } else { + throw new JSONException("create object error"); + } + + return object; + } + + public static ObjectReaderImplValue of(Class objectClass, Class valueClass, Method method) { + return new ObjectReaderImplValue(objectClass, valueClass, valueClass, 0L, null, null, null, null, method, null); + } + + public static ObjectReaderImplValue of(Class objectClass, Class valueClass, Function function) { + return new ObjectReaderImplValue(objectClass, valueClass, valueClass, 0L, null, null, null, null, null, function); + } +} diff --git a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderMisc.java b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderMisc.java index 332a776698..5e66ba7453 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderMisc.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderMisc.java @@ -6,7 +6,6 @@ import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.UnknownHostException; public class ObjectReaderMisc implements ObjectReader { @@ -47,20 +46,6 @@ public Object readObject(JSONReader jsonReader, long features) { return new InetSocketAddress(inetAddress, port); } - if (objectClass == InetAddress.class) { - String address = jsonReader.readString(); - try { - return InetAddress.getByName(address); - } catch (UnknownHostException e) { - throw new JSONException("read InetAddress error", e); - } - } - - if (objectClass == java.text.SimpleDateFormat.class) { - String str = jsonReader.readString(); - return new java.text.SimpleDateFormat(str); - } - throw new JSONException("not support : " + objectClass.getName()); } } diff --git a/core/src/main/java/com/alibaba/fastjson2/support/money/CurrencyUnitReader.java b/core/src/main/java/com/alibaba/fastjson2/support/money/CurrencyUnitReader.java deleted file mode 100644 index 8e0faa858b..0000000000 --- a/core/src/main/java/com/alibaba/fastjson2/support/money/CurrencyUnitReader.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.alibaba.fastjson2.support.money; - -import com.alibaba.fastjson2.JSONException; -import com.alibaba.fastjson2.JSONReader; -import com.alibaba.fastjson2.reader.ObjectReader; -import com.alibaba.fastjson2.util.TypeUtils; - -import java.lang.reflect.Method; - -final class CurrencyUnitReader - implements ObjectReader { - static Method METHOD_GET_CURRENCY; - - @Override - public Object readObject(JSONReader jsonReader, long features) { - String str = jsonReader.readString(); - - if (MoneySupport.CLASS_MONETARY == null) { - MoneySupport.CLASS_MONETARY = TypeUtils.loadClass("javax.money.Monetary"); - } - - if (METHOD_GET_CURRENCY == null) { - try { - METHOD_GET_CURRENCY = MoneySupport.CLASS_MONETARY.getMethod("getCurrency", String.class, String[].class); - } catch (NoSuchMethodException e) { - throw new JSONException("method not found : javax.money.Monetary.getCurrency", e); - } - } - - try { - return METHOD_GET_CURRENCY.invoke(null, str, new String[0]); - } catch (Exception e) { - throw new JSONException("getCurrency error", e); - } - } -} diff --git a/core/src/main/java/com/alibaba/fastjson2/support/money/MoneySupport.java b/core/src/main/java/com/alibaba/fastjson2/support/money/MoneySupport.java index d7117705c3..72e919b064 100644 --- a/core/src/main/java/com/alibaba/fastjson2/support/money/MoneySupport.java +++ b/core/src/main/java/com/alibaba/fastjson2/support/money/MoneySupport.java @@ -1,15 +1,13 @@ package com.alibaba.fastjson2.support.money; import com.alibaba.fastjson2.JSONException; -import com.alibaba.fastjson2.reader.FieldReader; -import com.alibaba.fastjson2.reader.ObjectReader; -import com.alibaba.fastjson2.reader.ObjectReaderCreator; -import com.alibaba.fastjson2.reader.ObjectReaderNoneDefaultConstrutor; +import com.alibaba.fastjson2.reader.*; import com.alibaba.fastjson2.util.TypeUtils; import com.alibaba.fastjson2.writer.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.math.BigDecimal; import java.util.Arrays; import java.util.Map; import java.util.function.Function; @@ -18,6 +16,7 @@ public class MoneySupport { static Class CLASS_MONETARY; static Class CLASS_MONETARY_AMOUNT; static Class CLASS_MONETARY_AMOUNT_FACTORY; + static Class CLASS_DEFAULT_NUMBER_VALUE; static Class CLASS_NUMBER_VALUE; static Class CLASS_CURRENCY_UNIT; @@ -28,8 +27,27 @@ public class MoneySupport { static Method METHOD_CLASS_MONETARY_AMOUNT_FACTORY_SET_NUMBER; static Method METHOD_CLASS_MONETARY_AMOUNT_FACTORY_CREATE; + static Method METHOD_GET_CURRENCY; + public static ObjectReader createCurrencyUnitReader() { - return new CurrencyUnitReader(); + if (CLASS_MONETARY == null) { + CLASS_MONETARY = TypeUtils.loadClass("javax.money.Monetary"); + } + + if (CLASS_CURRENCY_UNIT == null) { + CLASS_CURRENCY_UNIT = TypeUtils.loadClass("javax.money.CurrencyUnit"); + } + + if (METHOD_GET_CURRENCY == null) { + try { + METHOD_GET_CURRENCY = MoneySupport.CLASS_MONETARY.getMethod("getCurrency", String.class, String[].class); + } catch (NoSuchMethodException e) { + throw new JSONException("method not found : javax.money.Monetary.getCurrency", e); + } + } + + return ObjectReaderImplValue.of(CLASS_CURRENCY_UNIT, String.class, METHOD_GET_CURRENCY); +// return new CurrencyUnitReader(); } public static ObjectReader createMonetaryAmountReader() { @@ -50,7 +68,7 @@ public static ObjectReader createMonetaryAmountReader() { MoneySupport.class, MoneySupport.class, "currency", 0, 0, null, CLASS_CURRENCY_UNIT, CLASS_CURRENCY_UNIT, "currency", null, null ); FieldReader fieldReader1 = ObjectReaderCreator.INSTANCE.createFieldReaderParam( - MoneySupport.class, MoneySupport.class, "number", 0, 0, null, CLASS_NUMBER_VALUE, CLASS_NUMBER_VALUE, "number", null, null + MoneySupport.class, MoneySupport.class, "number", 0, 0, null, CLASS_DEFAULT_NUMBER_VALUE, CLASS_DEFAULT_NUMBER_VALUE, "number", null, null ); FieldReader[] fieldReaders = {fieldReader0, fieldReader1}; @@ -60,8 +78,26 @@ public static ObjectReader createMonetaryAmountReader() { } } + static Method METHOD_NUMBER_VALUE_OF; + public static ObjectReader createNumberValueReader() { - return new NumberValueReader(); + if (CLASS_DEFAULT_NUMBER_VALUE == null) { + CLASS_DEFAULT_NUMBER_VALUE = TypeUtils.loadClass("org.javamoney.moneta.spi.DefaultNumberValue"); + } + + if (METHOD_NUMBER_VALUE_OF == null) { + try { + METHOD_NUMBER_VALUE_OF = CLASS_DEFAULT_NUMBER_VALUE.getMethod("of", Number.class); + } catch (NoSuchMethodException e) { + throw new JSONException("method not found : org.javamoney.moneta.spi.DefaultNumberValue.of", e); + } + } + + if (CLASS_NUMBER_VALUE == null) { + CLASS_NUMBER_VALUE = TypeUtils.loadClass("javax.money.NumberValue"); + } + + return ObjectReaderImplValue.of(CLASS_NUMBER_VALUE, BigDecimal.class, METHOD_NUMBER_VALUE_OF); } public static ObjectWriter createMonetaryAmountWriter() { diff --git a/core/src/main/java/com/alibaba/fastjson2/support/money/NumberValueReader.java b/core/src/main/java/com/alibaba/fastjson2/support/money/NumberValueReader.java deleted file mode 100644 index ebb0846ee6..0000000000 --- a/core/src/main/java/com/alibaba/fastjson2/support/money/NumberValueReader.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.alibaba.fastjson2.support.money; - -import com.alibaba.fastjson2.JSONException; -import com.alibaba.fastjson2.JSONReader; -import com.alibaba.fastjson2.reader.ObjectReader; -import com.alibaba.fastjson2.util.TypeUtils; - -import java.lang.reflect.Method; -import java.math.BigDecimal; - -public class NumberValueReader - implements ObjectReader { - static Class CLASS_NUMBER_VALUE; - static Method METHOD_OF; - - @Override - public Object readObject(JSONReader jsonReader, long features) { - BigDecimal decimal = jsonReader.readBigDecimal(); - - if (CLASS_NUMBER_VALUE == null) { - CLASS_NUMBER_VALUE = TypeUtils.loadClass("org.javamoney.moneta.spi.DefaultNumberValue"); - } - - if (METHOD_OF == null) { - try { - METHOD_OF = CLASS_NUMBER_VALUE.getMethod("of", Number.class); - } catch (NoSuchMethodException e) { - throw new JSONException("method not found : org.javamoney.moneta.spi.DefaultNumberValue.of", e); - } - } - - try { - return METHOD_OF.invoke(null, decimal); - } catch (Exception e) { - throw new JSONException("create NumberValue error", e); - } - } -} diff --git a/core/src/test/java/com/alibaba/fastjson2/annotation/JSONFieldValueTest.java b/core/src/test/java/com/alibaba/fastjson2/annotation/JSONFieldValueTest.java new file mode 100644 index 0000000000..d3fa1af4c9 --- /dev/null +++ b/core/src/test/java/com/alibaba/fastjson2/annotation/JSONFieldValueTest.java @@ -0,0 +1,85 @@ +package com.alibaba.fastjson2.annotation; + +import com.alibaba.fastjson2.JSON; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class JSONFieldValueTest { + @Test + public void test() { + Bean bean = JSON.parseObject("\"abc\"", Bean.class); + assertEquals("abc", bean.value); + } + + public static class Bean { + private String value; + + public Bean(@JSONField(value = true) String value) { + this.value = value; + } + } + + @Test + public void test1() { + Bean1 bean = JSON.parseObject("\"abc\"", Bean1.class); + assertEquals("abc", bean.value); + } + + public static class Bean1 { + private String value; + + private Bean1(String value) { + this.value = value; + } + + @JSONCreator + public static Bean1 of(@JSONField(value = true) String value) { + return new Bean1(value); + } + } + + @Test + public void test2() { + Bean2 bean = JSON.parseObject("\"abc\"", Bean2.class); + assertEquals("abc", bean.value); + } + + public static class Bean2 { + private String value; + + @JSONCreator + public Bean2(@JSONField(value = true) String value) { + this.value = value; + } + + public Bean2(int value) { + throw new UnsupportedOperationException(); + } + } + + @Test + public void test3() { + JSON.mixIn(Bean3.class, Bean3Mixin.class); + Bean3 bean = JSON.parseObject("\"abc\"", Bean3.class); + assertEquals("abc", bean.value); + } + + public static class Bean3 { + private String value; + + public Bean3(String value) { + this.value = value; + } + + public Bean3(int value) { + throw new UnsupportedOperationException(); + } + } + + public static class Bean3Mixin { + @JSONCreator + public Bean3Mixin(@JSONField(value = true) String value) { + } + } +} diff --git a/core/src/test/java/com/alibaba/fastjson2/date/DateFormatTest.java b/core/src/test/java/com/alibaba/fastjson2/date/DateFormatTest.java index bd067e14d0..acfadcfe5a 100644 --- a/core/src/test/java/com/alibaba/fastjson2/date/DateFormatTest.java +++ b/core/src/test/java/com/alibaba/fastjson2/date/DateFormatTest.java @@ -5,6 +5,7 @@ import com.alibaba.fastjson2.annotation.JSONField; import org.junit.jupiter.api.Test; +import java.text.SimpleDateFormat; import java.util.Date; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -58,4 +59,13 @@ public static class Bean1 { @JSONField(format = "yyyy-MM-dd") public Date date; } + + @Test + public void testFormat0() { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + String json = JSON.toJSONString(format); + assertEquals("\"yyyy-MM-dd\"", json); + SimpleDateFormat format1 = JSON.parseObject(json, SimpleDateFormat.class); + assertEquals(format, format1); + } }