From a593a229bd51151d10e0ba7f77d40a5ad3a8d38d Mon Sep 17 00:00:00 2001 From: "shaojin.wensj" Date: Thu, 4 Aug 2022 20:15:20 +0800 Subject: [PATCH] Pair serialize & deserialize compatible with fastjson 1.x, fix issue #608 --- .../com/alibaba/fastjson2/JSONWriter.java | 6 +- .../reader/ObjectReaderBaseModule.java | 10 +- .../fastjson2/util/ApacheLang3Support.java | 269 ++++++++++++++++-- .../writer/ObjectWriterBaseModule.java | 50 ++-- .../autoType/AutoTypeTest46_Pair.java | 2 +- .../alibaba/fastjson2/issues/Issue608.java | 81 ++++++ .../util/ApacheLang3SupportTest.java | 2 - 7 files changed, 369 insertions(+), 51 deletions(-) create mode 100644 core/src/test/java/com/alibaba/fastjson2/issues/Issue608.java diff --git a/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java index dce6288817..10d926c7c8 100644 --- a/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java +++ b/core/src/main/java/com/alibaba/fastjson2/JSONWriter.java @@ -1474,7 +1474,11 @@ public enum Feature { */ NotWriteEmptyArray(1 << 25), WriteNonStringKeyAsString(1 << 26), - ErrorOnNoneSerializable(1 << 27); + ErrorOnNoneSerializable(1 << 27), + /** + * @since 2.0.11 + */ + WritePairAsJavaBean(1 << 28); public final long mask; 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 ca58774f0d..305a6e2964 100644 --- a/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java +++ b/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java @@ -194,10 +194,6 @@ public void getBeanInfo(BeanInfo beanInfo, Class objectClass) { if (mixInSource == null) { String typeName = objectClass.getName(); switch (typeName) { - case "org.apache.commons.lang3.tuple.Pair": - case "org.apache.commons.lang3.tuple.ImmutablePair": - provider.mixIn(objectClass, mixInSource = ApacheLang3Support.PairMixIn.class); - break; case "org.apache.commons.lang3.tuple.Triple": provider.mixIn(objectClass, mixInSource = ApacheLang3Support.TripleMixIn.class); break; @@ -1639,6 +1635,9 @@ public ObjectReader getObjectReader(ObjectReaderProvider provider, Type type) { return new ObjectReaderImplMapTyped((Class) rawType, HashMap.class, actualTypeParam0, actualTypeParam1, 0, GuavaSupport.singletonBiMapConverter()); case "org.springframework.util.LinkedMultiValueMap": return ObjectReaderImplMap.of(type, (Class) rawType, 0L); + case "org.apache.commons.lang3.tuple.Pair": + case "org.apache.commons.lang3.tuple.ImmutablePair": + return new ApacheLang3Support.PairReader((Class) rawType, actualTypeParam0, actualTypeParam1); default: break; } @@ -1797,6 +1796,9 @@ public ObjectReader getObjectReader(ObjectReaderProvider provider, Type type) { return new ObjectReaderException((Class) type); } break; + case "org.apache.commons.lang3.tuple.Pair": + case "org.apache.commons.lang3.tuple.ImmutablePair": + return new ApacheLang3Support.PairReader((Class) type, Object.class, Object.class); default: break; } diff --git a/core/src/main/java/com/alibaba/fastjson2/util/ApacheLang3Support.java b/core/src/main/java/com/alibaba/fastjson2/util/ApacheLang3Support.java index e160362dfb..d925af54f8 100644 --- a/core/src/main/java/com/alibaba/fastjson2/util/ApacheLang3Support.java +++ b/core/src/main/java/com/alibaba/fastjson2/util/ApacheLang3Support.java @@ -1,39 +1,268 @@ package com.alibaba.fastjson2.util; +import com.alibaba.fastjson2.JSONB; +import com.alibaba.fastjson2.JSONException; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; import com.alibaba.fastjson2.annotation.JSONCreator; -import com.alibaba.fastjson2.annotation.JSONField; -import com.alibaba.fastjson2.annotation.JSONType; +import com.alibaba.fastjson2.reader.ObjectReader; +import com.alibaba.fastjson2.writer.ObjectWriter; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import static com.alibaba.fastjson2.JSONB.Constants.BC_TYPED_ANY; public interface ApacheLang3Support { - @JSONType(typeName = "org.apache.commons.lang3.tuple.Pair") - interface PairMixIn { + interface TripleMixIn { @JSONCreator - static Object of(L left, R right) { + static Object of(L left, M middle, R right) { return null; } - - @JSONField(deserialize = false) - Object setValue(Object value); } - interface MutablePairMixIn { - @JSONCreator - static Object of(L left, R right) { - return null; + class PairReader + implements ObjectReader { + static final long LEFT = Fnv.hashCode64("left"); + static final long RIGHT = Fnv.hashCode64("right"); + + static final long PAIR = Fnv.hashCode64("org.apache.commons.lang3.tuple.Pair"); + static final long MUTABLE_PAIR = Fnv.hashCode64("org.apache.commons.lang3.tuple.MutablePair"); + static final long IMMUTABLE_PAIR = Fnv.hashCode64("org.apache.commons.lang3.tuple.ImmutablePair"); + + final Class objectClass; + final Type leftType; + final Type rightType; + + final Method of; + + public PairReader(Class objectClass, Type leftType, Type rightType) { + this.objectClass = objectClass; + this.leftType = leftType; + this.rightType = rightType; + + try { + of = objectClass.getMethod("of", Object.class, Object.class); + } catch (NoSuchMethodException e) { + throw new JSONException("Pair.of method not found", e); + } + } + + @Override + public Object readJSONBObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) { + if (jsonReader.nextIfNull()) { + return null; + } + + if (jsonReader.nextIfMatch(BC_TYPED_ANY)) { + long typeHash = jsonReader.readTypeHashCode(); + if (typeHash != PAIR && typeHash != IMMUTABLE_PAIR && typeHash != MUTABLE_PAIR) { + throw new JSONException("not support inputType : " + jsonReader.getString()); + } + } + + Object left = null, right = null; + if (jsonReader.nextIfObjectStart()) { + for (int i = 0; i < 100; i++) { + if (jsonReader.nextIfObjectEnd()) { + break; + } + if (jsonReader.isString()) { + long hashCode = jsonReader.readFieldNameHashCode(); + if (hashCode == LEFT) { + left = jsonReader.read(leftType); + } else if (hashCode == RIGHT) { + right = jsonReader.read(rightType); + } else if (i == 0) { + left = jsonReader.getFieldName(); + right = jsonReader.read(rightType); + } else { + jsonReader.skipValue(); + } + } else if (i == 0) { + left = jsonReader.read(leftType); + right = jsonReader.read(rightType); + } else { + throw new JSONException(jsonReader.info("not support input")); + } + } + } else if (jsonReader.isArray()) { + int len = jsonReader.startArray(); + if (len != 2) { + throw new JSONException(jsonReader.info("not support input")); + } + left = jsonReader.read(leftType); + right = jsonReader.read(rightType); + } else { + throw new JSONException(jsonReader.info("not support input")); + } + + try { + return of.invoke(null, left, right); + } catch (Exception e) { + throw new JSONException("create pair error", e); + } } - Object getLeft(); + @Override + public Object readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) { + if (jsonReader.nextIfNull()) { + return null; + } - Object getRight(); + Object left = null, right = null; + if (jsonReader.nextIfObjectStart()) { + for (int i = 0; i < 100; i++) { + if (jsonReader.nextIfObjectEnd()) { + break; + } + if (jsonReader.isString()) { + long hashCode = jsonReader.readFieldNameHashCode(); + if (hashCode == LEFT) { + left = jsonReader.read(leftType); + } else if (hashCode == RIGHT) { + right = jsonReader.read(rightType); + } else if (i == 0) { + left = jsonReader.getFieldName(); + jsonReader.nextIfMatch(':'); + right = jsonReader.read(rightType); + } else { + jsonReader.skipValue(); + } + } else if (i == 0) { + left = jsonReader.read(leftType); + jsonReader.nextIfMatch(':'); + right = jsonReader.read(rightType); + } else { + throw new JSONException(jsonReader.info("not support input")); + } + } + } else if (jsonReader.nextIfMatch('[')) { + left = jsonReader.read(leftType); + right = jsonReader.read(rightType); + if (!jsonReader.nextIfMatch(']')) { + throw new JSONException(jsonReader.info("not support input")); + } + } else { + throw new JSONException(jsonReader.info("not support input")); + } - @JSONField(deserialize = false) - Object setValue(Object value); + try { + return of.invoke(null, left, right); + } catch (Exception e) { + throw new JSONException("create pair error", e); + } + } } - interface TripleMixIn { - @JSONCreator - static Object of(L left, M middle, R right) { - return null; + class PairWriter + implements ObjectWriter { + final Class objectClass; + final String typeName; + final long typeNameHash; + Method leftMethod; + Method rightMethod; + + byte[] nameWithColonUTF8; + char[] nameWithColonUTF16; + byte[] typeNameJSONB; + + static byte[] leftName = JSONB.toBytes("left"); + static byte[] rightName = JSONB.toBytes("right"); + + public PairWriter(Class objectClass) { + this.objectClass = objectClass; + this.typeName = objectClass.getName(); + this.typeNameHash = Fnv.hashCode64(typeName); + } + + @Override + public void writeJSONB(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) { + if (object == null) { + jsonWriter.writeNull(); + return; + } + + if ((jsonWriter.getFeatures(features) & JSONWriter.Feature.WriteClassName.mask) != 0) { + if (typeNameJSONB == null) { + typeNameJSONB = JSONB.toBytes(typeName); + } + jsonWriter.writeTypeName(typeNameJSONB, typeNameHash); + } + + jsonWriter.startObject(); + + Object left = getLeft(object); + Object right = getRight(object); + + jsonWriter.writeNameRaw(leftName, PairReader.LEFT); + jsonWriter.writeAny(left); + + jsonWriter.writeNameRaw(rightName, PairReader.RIGHT); + jsonWriter.writeAny(right); + jsonWriter.endObject(); + } + + @Override + public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) { + if (object == null) { + jsonWriter.writeNull(); + return; + } + + Object left = getLeft(object); + Object right = getRight(object); + + jsonWriter.startObject(); + if ((jsonWriter.getFeatures(features) & JSONWriter.Feature.WritePairAsJavaBean.mask) != 0) { + jsonWriter.writeName("left"); + jsonWriter.writeColon(); + jsonWriter.writeAny(left); + + jsonWriter.writeName("right"); + jsonWriter.writeColon(); + jsonWriter.writeAny(right); + } else { + jsonWriter.writeNameAny(left); + jsonWriter.writeColon(); + jsonWriter.writeAny(right); + } + + jsonWriter.endObject(); + } + + Object getLeft(Object object) { + Class objectClass = object.getClass(); + if (leftMethod == null) { + try { + leftMethod = objectClass.getMethod("getLeft"); + } catch (NoSuchMethodException e) { + throw new JSONException("getLeft method not found", e); + } + } + + try { + return leftMethod.invoke(object); + } catch (Exception e) { + throw new JSONException("invoke getLeft method error", e); + } + } + + Object getRight(Object object) { + Class objectClass = object.getClass(); + if (rightMethod == null) { + try { + rightMethod = objectClass.getMethod("getRight"); + } catch (NoSuchMethodException e) { + throw new JSONException("getRight method not found", e); + } + } + + try { + return rightMethod.invoke(object); + } catch (Exception e) { + throw new JSONException("invoke getRight method error", e); + } } } } diff --git a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java index 50675e111a..dba5137184 100644 --- a/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java +++ b/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterBaseModule.java @@ -88,19 +88,19 @@ public void getBeanInfo(BeanInfo beanInfo, Class objectClass) { if (jsonType == null) { Class mixInSource = provider.mixInCache.get(objectClass); - if (mixInSource == null) { - String typeName = objectClass.getName(); - switch (typeName) { - case "org.apache.commons.lang3.tuple.ImmutablePair": - provider.mixIn(objectClass, mixInSource = ApacheLang3Support.PairMixIn.class); - break; - case "org.apache.commons.lang3.tuple.MutablePair": - provider.mixIn(objectClass, mixInSource = ApacheLang3Support.MutablePairMixIn.class); - break; - default: - break; - } - } +// if (mixInSource == null) { +// String typeName = objectClass.getName(); +// switch (typeName) { +// case "org.apache.commons.lang3.tuple.ImmutablePair": +// provider.mixIn(objectClass, mixInSource = ApacheLang3Support.PairMixIn.class); +// break; +// case "org.apache.commons.lang3.tuple.MutablePair": +// provider.mixIn(objectClass, mixInSource = ApacheLang3Support.MutablePairMixIn.class); +// break; +// default: +// break; +// } +// } if (mixInSource != null) { beanInfo.mixIn = true; @@ -201,16 +201,16 @@ public void getBeanInfo(BeanInfo beanInfo, Class objectClass) { @Override public void getFieldInfo(BeanInfo beanInfo, FieldInfo fieldInfo, Class objectType, Field field) { Class mixInSource = provider.mixInCache.get(objectType); - if (objectType != null) { - String typeName = objectType.getName(); - switch (typeName) { - case "org.apache.commons.lang3.tuple.ImmutablePair": - provider.mixIn(objectType, mixInSource = ApacheLang3Support.PairMixIn.class); - break; - default: - break; - } - } +// if (objectType != null) { +// String typeName = objectType.getName(); +// switch (typeName) { +// case "org.apache.commons.lang3.tuple.ImmutablePair": +// provider.mixIn(objectType, mixInSource = ApacheLang3Support.PairMixIn.class); +// break; +// default: +// break; +// } +// } if (mixInSource != null && mixInSource != objectType) { Field mixInField = null; @@ -873,6 +873,10 @@ public ObjectWriter getObjectWriter(Type objectType, Class objectClass) { case "java.net.InetSocketAddress": case "java.text.SimpleDateFormat": return ObjectWriterMisc.INSTANCE; + case "org.apache.commons.lang3.tuple.Pair": + case "org.apache.commons.lang3.tuple.MutablePair": + case "org.apache.commons.lang3.tuple.ImmutablePair": + return new ApacheLang3Support.PairWriter(objectClass); default: break; } diff --git a/core/src/test/java/com/alibaba/fastjson2/autoType/AutoTypeTest46_Pair.java b/core/src/test/java/com/alibaba/fastjson2/autoType/AutoTypeTest46_Pair.java index 0b239fc0f9..799b82a1a3 100644 --- a/core/src/test/java/com/alibaba/fastjson2/autoType/AutoTypeTest46_Pair.java +++ b/core/src/test/java/com/alibaba/fastjson2/autoType/AutoTypeTest46_Pair.java @@ -33,7 +33,7 @@ public void test_1() throws Exception { "\t\"@type\":\"com.alibaba.fastjson2.autoType.AutoTypeTest46_Pair$Bean#0\",\n" + "\t\"@value\":{\n" + "\t\t\"pair1#1\":{\n" + - "\t\t\t\"@type\":\"org.apache.commons.lang3.tuple.Pair#2\",\n" + + "\t\t\t\"@type\":\"org.apache.commons.lang3.tuple.ImmutablePair#2\",\n" + "\t\t\t\"@value\":{\n" + "\t\t\t\t\"left#3\":\"101\",\n" + "\t\t\t\t\"right#4\":true\n" + diff --git a/core/src/test/java/com/alibaba/fastjson2/issues/Issue608.java b/core/src/test/java/com/alibaba/fastjson2/issues/Issue608.java new file mode 100644 index 0000000000..c5ba6301f8 --- /dev/null +++ b/core/src/test/java/com/alibaba/fastjson2/issues/Issue608.java @@ -0,0 +1,81 @@ +package com.alibaba.fastjson2.issues; + +import com.alibaba.fastjson2.*; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class Issue608 { + @Test + public void pair_serialize() { + assertEquals( + "{\"aaa\":\"bbb\"}", + JSON.toJSONString(Pair.of("aaa", "bbb")) + ); + + assertEquals( + "{\"left\":\"aaa\",\"right\":\"bbb\"}", + JSON.toJSONString(Pair.of("aaa", "bbb"), JSONWriter.Feature.WritePairAsJavaBean) + ); + } + + @Test + public void pair_deserialize() { + Pair pair = JSON.parseObject("{\"aaa\":\"bbb\"}", Pair.class); + assertEquals("aaa", pair.getLeft()); + assertEquals("bbb", pair.getRight()); + } + + @Test + public void pair_deserialize_jsonb() { + byte[] jsonbBytes = JSONObject.of("aaa", "bbb").toJSONBBytes(); + Pair pair = JSONB.parseObject(jsonbBytes, Pair.class); + assertEquals("aaa", pair.getLeft()); + assertEquals("bbb", pair.getRight()); + } + + @Test + public void pair_deserialize1() { + Pair pair = JSON.parseObject("{\"aaa\":\"123\"}", new TypeReference>() { + }); + assertEquals(123, pair.getRight()); + } + + @Test + public void pair_deserialize1_jsonb() { + byte[] jsonbBytes = JSONObject.of("aaa", "123").toJSONBBytes(); + Pair pair = JSONB.parseObject(jsonbBytes, new TypeReference>() { + }); + assertEquals(123, pair.getRight()); + } + + @Test + public void pair_deserialize2() { + Pair pair = JSON.parseObject("{\"left\":\"aaa\",\"right\":\"123\"}", new TypeReference>() { + }); + assertEquals(123, pair.getRight()); + } + + @Test + public void pair_deserialize2_jsonb() { + byte[] jsonbBytes = JSONObject.of("left", "aaa", "right", "123").toJSONBBytes(); + Pair pair = JSONB.parseObject(jsonbBytes, new TypeReference>() { + }); + assertEquals(123, pair.getRight()); + } + + @Test + public void pair_deserialize3() { + Pair pair = JSON.parseObject("{\"left\":\"aaa\",\"right\":\"123\",\"x\":123}", new TypeReference>() { + }); + assertEquals(123, pair.getRight()); + } + + @Test + public void pair_deserialize4() { + Pair pair = JSON.parseObject("[\"aaa\",\"123\"]", new TypeReference>() { + }); + assertEquals(123, pair.getRight()); + } +} diff --git a/core/src/test/java/com/alibaba/fastjson2/util/ApacheLang3SupportTest.java b/core/src/test/java/com/alibaba/fastjson2/util/ApacheLang3SupportTest.java index fe5768448a..fab236e708 100644 --- a/core/src/test/java/com/alibaba/fastjson2/util/ApacheLang3SupportTest.java +++ b/core/src/test/java/com/alibaba/fastjson2/util/ApacheLang3SupportTest.java @@ -7,8 +7,6 @@ public class ApacheLang3SupportTest { @Test public void test() { - assertNull(ApacheLang3Support.MutablePairMixIn.of(null, null)); - assertNull(ApacheLang3Support.PairMixIn.of(null, null)); assertNull(ApacheLang3Support.TripleMixIn.of(null, null, null)); } }