alibaba / fastjson2

🚄 FASTJSON2 is a Java JSON library with excellent performance.
Apache License 2.0
3.81k stars 499 forks source link

[BUG]扩展Enum序列化器不起作用 #690

Closed leonchen83 closed 2 years ago

leonchen83 commented 2 years ago

问题描述

简要描述您碰到的问题。

环境信息

请填写以下信息:

重现步骤

如何操作可以重现该问题:

main.class

import static com.alibaba.fastjson2.JSONWriter.Feature.WriteNonStringKeyAsString;
import static com.alibaba.fastjson2.JSONWriter.Feature.WriteNulls;

import java.io.IOException;
import java.lang.reflect.Type;

import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.reader.ObjectReader;
import com.alibaba.fastjson2.reader.ObjectReaderProvider;
import com.alibaba.fastjson2.util.TypeUtils;
import com.alibaba.fastjson2.writer.ObjectWriter;
import com.alibaba.fastjson2.writer.ObjectWriterProvider;

public class Main {
    public static void main(String[] args) throws IOException {
        Test t = new Test();
        t.setInt32(Misc.IntEnum.CLOSE);
        t.setInt64(Misc.LongEnum.HIGH);

        Main main = new Main();

        //序列化XWriterProvider扩展不起作用
        String s = main.write(t);
        System.out.println(s); // actual {"int32":1,"int64":0} expect {"int32":10,"int64":100}

        //反序列化扩展正常
        String json = "{\"int32\":10,\"int64\":100}";
        Test tt = main.read(json, Test.class);
        System.out.println(tt.int32);
        System.out.println(tt.int64);
    }

    public <T> T read(String json, Type type) {
        JSONReader.Context context = new JSONReader.Context(new XReaderProvider());
        try (final JSONReader reader = JSONReader.of(context, json)) {
            ObjectReader<T> v = reader.getObjectReader(type);
            return v.readObject(reader, 0);
        }
    }

    public String write(Object value) {
        JSONWriter.Context context = new JSONWriter.Context(new XWriterProvider());
        try (final JSONWriter writer = JSONWriter.of(context)) {
            if (value == null) writer.writeNull();
            else {
                context.config(WriteNonStringKeyAsString);
                context.config(WriteNulls);
                final Class<?> clazz = value.getClass();
                ObjectWriter<?> v = writer.getObjectWriter(clazz, clazz);
                v.write(writer, value, null, null, 0);
            }
            return writer.toString();
        }
    }

    public class XWriterProvider extends ObjectWriterProvider {

        @Override
        public ObjectWriter getObjectWriter(Type objectType, Class objectClass, boolean fieldBased) {
            final boolean persistent = Misc.PersistentEnum.class.isAssignableFrom(objectClass);
            if (persistent) return new PersistentEnumSerializer();
            else return super.getObjectWriter(objectType, objectClass, fieldBased);
        }
    }

    public class XReaderProvider extends ObjectReaderProvider {

        @Override
        public ObjectReader getObjectReader(Type objectType, boolean fieldBased) {
            Class<?> clazz = TypeUtils.getMapping(objectType);
            final boolean persistent = Misc.PersistentEnum.class.isAssignableFrom(clazz);
            if (persistent) return new PersistentEnumDeserializer();
            else return super.getObjectReader(objectType, fieldBased);
        }
    }

    public static class Test {
        Misc.IntEnum int32;
        Misc.LongEnum int64;

        public Misc.IntEnum getInt32() {
            return int32;
        }

        public void setInt32(Misc.IntEnum int32) {
            this.int32 = int32;
        }

        public Misc.LongEnum getInt64() {
            return int64;
        }

        public void setInt64(Misc.LongEnum int64) {
            this.int64 = int64;
        }
    }

    public static class PersistentEnumDeserializer<T extends Enum<T> & Misc.PersistentEnum<?>> implements ObjectReader<T> {

        @Override
        public T readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
            final Type t = Misc.PersistentEnum.getTypeArgument((Class<Misc.PersistentEnum<?>>) fieldType);
            Object value = jsonReader.readAny();
            if (t == Integer.class)
                return Misc.PersistentEnum.parse((Class<T>) fieldType, Integer.valueOf(value.toString()));
            if (t == Long.class)
                return Misc.PersistentEnum.parse((Class<T>) fieldType, Long.valueOf(value.toString()));
            throw new UnsupportedOperationException();
        }
    }

    public static class PersistentEnumSerializer implements ObjectWriter<Misc.PersistentEnum<?>> {

        @Override
        public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
            Object value = ((Misc.PersistentEnum<?>) object).getValue();
            if (value instanceof Integer) {
                jsonWriter.writeInt32((Integer) value);
            } else if (value instanceof Long) {
                jsonWriter.writeInt64((Long) value);
            }
        }
    }
}

misc.class

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import jdk.internal.misc.JavaLangAccess;
import jdk.internal.misc.SharedSecrets;

public class Misc {

    public static enum IntEnum implements PersistentEnum<Integer> {
        OPEN(6), CLOSE(10);

        int value;

        IntEnum(int value) {
            this.value = value;
        }

        @Override
        public Integer getValue() {
            return value;
        }

        @Override
        public Map<Integer, IntEnum> getAll() {
            Map<Integer, IntEnum> map = new HashMap<>();
            map.put(6, OPEN);
            map.put(10, CLOSE);
            return map;
        }

        public static IntEnum parse(int value) {
            if (value == OPEN.value) return OPEN;
            else if (value == CLOSE.value) return CLOSE;
            else throw new UnsupportedOperationException();
        }
    }

    public static enum LongEnum implements PersistentEnum<Long> {
        HIGH(100L), LOW(200L);

        long value;

        LongEnum(long value) {
            this.value = value;
        }

        @Override
        public Long getValue() {
            return value;
        }

        @Override
        public Map<Long, LongEnum> getAll() {
            Map<Long, LongEnum> map = new HashMap<>();
            map.put(100L, HIGH);
            map.put(200L, LOW);
            return map;
        }

        public static LongEnum parse(int value) {
            if (value == HIGH.value) return HIGH;
            else if (value == LOW.value) return LOW;
            else throw new UnsupportedOperationException();
        }
    }

    public interface PersistentEnum<T> {
        T getValue();

        Map<T, ? extends PersistentEnum<T>> getAll();

        static JavaLangAccess ACCESS = SharedSecrets.getJavaLangAccess();

        public static <T extends PersistentEnum<?>> Type getTypeArgument(Class<T> clazz) {
            //
            Type vs[] = clazz.getGenericInterfaces();
            if ((vs == null || vs.length == 0)) {
                vs = clazz.getEnclosingClass().getGenericInterfaces();
            }

            //
            ParameterizedType pt;
            for (int i = 0, n = vs.length; i < n; i++) {
                if (!(vs[i] instanceof ParameterizedType)) {
                    continue;
                }
                pt = (ParameterizedType) vs[i];
                final Type rt = pt.getRawType();
                if (rt != PersistentEnum.class) continue;
                return pt.getActualTypeArguments()[0];
            }
            return null;
        }

        public static <T extends Enum<T> & PersistentEnum<?>> T parse(final Class<T> clazz, Object value) {
            if (value == null) return null;
            T[] constants = ACCESS.getEnumConstantsShared(clazz);
            T r = (constants == null || constants.length == 0) ? null : (T) ((PersistentEnum<?>) constants[0]).getAll().get(value);
            return r;
        }
    }

}

序列化时期待如下结果

expect {"int32":10,"int64":100}

实际

actual {"int32":1,"int64":0} 

期待的正确结果

对您期望发生的结果进行清晰简洁的描述。

相关日志输出

请复制并粘贴任何相关的日志输出。

附加信息

如果你还有其他需要提供的信息,可以在这里填写(可以提供截图、视频等)。

ifeng113 commented 2 years ago

我也遇到了相同的问题: os:windows 11 jdk:jdk 11 jar:fastjson 2.0.11

wenshao commented 2 years ago
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.annotation.JSONField;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class EnumCustomTest {
    @Test
    public void test() {
        Bean bean = new Bean();
        bean.size = Size.Large;
        String str = JSON.toJSONString(bean);
        assertEquals("{\"size\":101}", str);

        Bean bean1 = JSON.parseObject(str, Bean.class);
        assertEquals(bean.size, bean1.size);
    }

    public static class Bean {
        public Size size;
    }

    public interface XEnum {
        @JSONField(value = true)
        int getValue();
    }

    public enum Size implements XEnum {
        Small(99),
        Medium(100),
        Large(101),
        XLarge(102);

        private final int value;

        Size(int value) {
            this.value = value;
        }

        @Override
        public int getValue() {
            return value;
        }
    }
}

如果XEnum这个类是在第三方类库中,不能修改,使用Mixin ( https://github.com/alibaba/fastjson2/blob/main/docs/mixin_cn.md ),比如:

public interface XEnumMixin {
      @JSONField(value = true)
      int getValue();
  }

JSON.mixIn(XEnum.class, XEnumMixin.class);

@leonchen83 看下上面这个是不是你想要的。Enum类型支持通过配置@JSONField(value=true)指定序列化用的字段,反序列化也会自动识别这个字段。

支持在interface上配置JSONField,需要用最新的快照版本 https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/fastjson2/2.0.12-SNAPSHOT/

2.0.12预计在8月21日前发布

leonchen83 commented 2 years ago

@wenshao 我们需要为数百个特殊的Enum类做一个序列化与反序列化器,而且不能加JSONField属性,希望fastjson2提供一个入口可以定制自己的Enum序列化与反序列化器,我的理解应该在getObjectReader, getObjectWriter这里扩展就好,不过现实是并不生效

wenshao commented 2 years ago

@leonchen83 可以统一配置在PersistentEnum#getValue方法上

public interface PersistentEnum<T> {
                 @JSONField(value = true)
        T getValue();
leonchen83 commented 2 years ago

@wenshao 是这样的。我们不仅仅有一个json序列化器与反序列化器,我们同时有jackson和fastjson。不能把fastjson的@JSONField(value = true)放到PersistentEnum#getValue上,这样代码中就强依赖fastjson的部分注解了。所以我们一般通过编码的方式来做这件事,既不强依赖jackson也不强依赖fastjson

leonchen83 commented 2 years ago

比如说在jackson中,我们是这样扩展的

public class SimpleDeserializersEx extends SimpleDeserializers {
    //
    private final ObjectMappers mapper;
    private final JsonDeserializer<?> bitwise;
    private final JsonDeserializer<?> persistent;
    private static final long serialVersionUID = 1L;

    /**
     * 
     */
    public SimpleDeserializersEx(ObjectMappers mapper) {
        this.mapper = mapper;
        bitwise = new BitwiseEnumDeserializer<>();
        persistent = new PersistentEnumDeserializer<>();
    }

    /**
     * 
     */
    @Override
    public JsonDeserializer<?> findEnumDeserializer (Class<?> t, DeserializationConfig c, BeanDescription bean)
    throws JsonMappingException {
        boolean persistent = this.mapper.isPersistentEnumEnabled() && PersistentEnum.class.isAssignableFrom(t);
        if(persistent) return this.persistent; else return super.findEnumDeserializer(t, c, bean);/* default */
    }

    @Override
    public JsonDeserializer<?> findBeanDeserializer (JavaType t, DeserializationConfig c, BeanDescription bean)
    throws JsonMappingException {
        boolean bitwise = mapper.isBitwiseEnumEnabled() && BitwiseEnum.class.isAssignableFrom(t.getRawClass());
        if (bitwise) { return this.bitwise; } else return super.findBeanDeserializer(t, c, bean); /* default */
    }
}
public class SimpleSerializersEx extends SimpleSerializers {
    //
    private final ObjectMappers mapper;
    private BitwiseEnumSerializer bitwise;
    private PersistentEnumSerializer persistent;
    private static final long serialVersionUID = 1L;

    /**
     * 
     */
    public SimpleSerializersEx ( ObjectMappers mapper ) {
        this.mapper = mapper;
        this.bitwise = new BitwiseEnumSerializer();
        this.persistent = new PersistentEnumSerializer();
    }

    /**
     * 
     */
    @Override
    public JsonSerializer<?> findSerializer ( SerializationConfig config, JavaType type, BeanDescription desc ) {
        final Class<?> v = type.getRawClass();
        if (mapper.isPersistentEnumEnabled() && PersistentEnum.class.isAssignableFrom(v)) return this.persistent;
        else if(this.mapper.isBitwiseEnumEnabled() && BitwiseEnum.class.isAssignableFrom(v)) return this.bitwise;
        else return super.findSerializer(config, type, desc);
    }
}
wenshao commented 2 years ago

声明一个PersistentEnum对应的Mixin类:

public interface PersistentEnumMixin {
    @JSONField(value = true)
    T getValue();
}

注册Mixin

JSON.mixIn(PersistentEnum.class, PersistentEnumMixin.class);

通过这种方法,也使得代码不直接依赖fastjson,这样的自定义逻辑更简洁

leonchen83 commented 2 years ago

@wenshao 这种方式应该可以,但是我很奇怪为啥

    public class XWriterProvider extends ObjectWriterProvider {

        @Override
        public ObjectWriter getObjectWriter(Type objectType, Class objectClass, boolean fieldBased) {
            final boolean persistent = Misc.PersistentEnum.class.isAssignableFrom(objectClass);
            if (persistent) return new PersistentEnumSerializer();
            else return super.getObjectWriter(objectType, objectClass, fieldBased);
        }
    }

这个处理找不到对应的Enum序列化器

wenshao commented 2 years ago

https://github.com/alibaba/fastjson2/releases/tag/2.0.12 请用新版本