alibaba / fastjson2

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

对 ObjectWriterCreatorASM 中 enumValueField == null 条件的疑问 #1347

Open houkunlin opened 1 year ago

houkunlin commented 1 year ago

对下面代码块中的 enumValueField == null 条件判断有点疑问。

https://github.com/alibaba/fastjson2/blob/1ca0ab6f7e468cf8174ef048df3743665e9262bd/core/src/main/java/com/alibaba/fastjson2/writer/ObjectWriterCreatorASM.java#L3399-L3419

我在使用 Dubbo 时,遇到一个枚举无法序列化问题,经过排查后发现,我的 enumValueField 得到了一个 getValue() 的 Method 对象,并且 String[] enumAnnotationNames = BeanUtils.getEnumAnnotationNames(fieldClass); 值为null,因此这个枚举最终使用了 FieldWriterObject 来处理

enumValueField 的 getValue() 拿到的是使用了 @com.fasterxml.jackson.annotation.JsonValue 注解的方法。

在我对此代码修改后进行本地覆盖运行项目时,一切正常,我修改后的代码如下,仅仅只是调整了条件判断,我改了后我项目中的枚举能够正常序列化,但是我不知道会不会有其他未知的负面问题:

if (fieldClass.isEnum()) {
    BeanInfo beanInfo = new BeanInfo();
    provider.getBeanInfo(beanInfo, fieldClass);

    boolean writeEnumAsJavaBean = beanInfo.writeEnumAsJavaBean;
    if (!writeEnumAsJavaBean) {
        ObjectWriter objectWriter = provider.cache.get(fieldClass);
        if (objectWriter != null && !(objectWriter instanceof ObjectWriterImplEnum)) {
            writeEnumAsJavaBean = true;
        }
    }

    Member enumValueField = BeanUtils.getEnumValueField(fieldClass, provider);

    if (!writeEnumAsJavaBean) {
        if (enumValueField == null) {
            String[] enumAnnotationNames = BeanUtils.getEnumAnnotationNames(fieldClass);
            if (enumAnnotationNames == null) {
                return new FieldWriterEnum(fieldName, ordinal, features, format, label, fieldType, (Class<? extends Enum>) fieldClass, field, null);
            }
        } else {
            return new FieldWriterEnum(fieldName, ordinal, features, format, label, fieldType, (Class<? extends Enum>) fieldClass, field, null);
        }
    }
}

我的枚举相关类如下:

@Data
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
@FieldNameConstants
@NoArgsConstructor
@AllArgsConstructor
public class LoginAccountVo extends BaseEntityVo {
    /**
     * 主键
     */
    private Long id;
    /**
     * 关联用户ID
     */
    private Long userId;
    /**
     * 登录标识(系统规则):帐号(用户名、手机号、电子邮箱等)实际账号。
     */
    private String identifier;
    /**
     * 登录标识(区分大小写):帐号(用户名、手机号、电子邮箱等)展示信息,用户实际输入内容
     */
    private String identifierDisplay;
    /**
     * 登录凭据:密码
     */
    private String credential;
    /**
     * 帐号类型:用户名、手机号、电子邮箱
     */
    private LoginAccountType type;
    /**
     * 是否是帐内帐号
     */
    private boolean local;
    /**
     * 最后一次登录IP
     */
    private String lastLoginIp;
    /**
     * 最后一次登录时间
     */
    private LocalDateTime lastLoginTime;

    public void setType(final LoginAccountType type) {
        this.type = type;
        this.local = type.isLocal();
    }
}
@Getter
@DictConverter
@AllArgsConstructor
public enum LoginAccountType implements DictEnum<Integer>, IEnum<Integer>, Serializable {
    /**
     * 用户名
     */
    USERNAME(1, "用户名", true),
    /**
     * 手机号
     */
    PHONE(2, "手机号", true),
    /**
     * 电子邮件
     */
    EMAIL(3, "电子邮件", true)
    ;
    private final Integer value;
    private final String title;
    // 是否是本地账号
    private final boolean local;

    @com.fasterxml.jackson.annotation.JsonCreator
    public static LoginAccountType create(Integer value) {
        return DictEnum.valueOf(values(), value);
    }

    /**
     * 处理登录标识,统一转换大小写问题
     *
     * @param identifier 登录标识
     * @return 结果
     */
    public String handleIdentifier(final String identifier) {
        if (!local) {
            // 不是本地账号,第三方账号需要区分大小写
            return identifier;
        }
        return identifier == null ? null : identifier.toLowerCase();
    }
}
/**
 * 数据字典枚举接口。系统字典枚举接口
 *
 * @author HouKunLin
 */
public interface DictEnum<T extends Serializable> {
    /**
     * 通过枚举值从枚举列表中获取枚举对象
     *
     * @param values 枚举对象列表
     * @param value  枚举值
     * @param <T>    枚举值类型
     * @return 枚举对象
     */
    static <T extends Serializable, E extends Enum<E> & DictEnum<T>> E valueOf(E[] values, T value) {
        for (final E enums : values) {
            if (enums.getValue().equals(value)) {
                return enums;
            }
        }
        return null;
    }

    /**
     * 字典值
     *
     * @return 字典值
     */
    @com.fasterxml.jackson.annotation.JsonValue
    T getValue();

    /**
     * 字典文本
     *
     * @return 字典文本
     */
    String getTitle();

    /**
     * 判断字典值是否相等
     *
     * @param o 传入的值,可为当前的枚举对象
     * @return 判断是否相等
     */
    default boolean eq(Object o) {
        return this == o || Objects.equal(o, getValue());
    }
}

package com.baomidou.mybatisplus.annotation;
public interface IEnum<T extends Serializable> {

    /**
     * 枚举数据库存储值
     */
    T getValue();
}
wenshao commented 1 year ago

这个的行为,在jackson中也是一样的,比如:

@Test
    public void test() throws Exception {
        Bean bean = new Bean();
        bean.accountType = LoginAccountType.PHONE;
        assertEquals("{\"accountType\":2}", JSON.toJSONString(bean));
        System.out.println(new ObjectMapper().writeValueAsString(bean));
    }

    public static class Bean {
        public LoginAccountType accountType;
    }
houkunlin commented 1 year ago

可是在 ASM 中,Member enumValueField = BeanUtils.getEnumValueField(fieldClass, provider) 获取到了枚举类的 @com.fasterxml.jackson.annotation.JsonValue 方法对象,由于 if(enumValueField == null) 条件存在,因此导致了这本是一个枚举类,但是却没有使用 FieldWriterEnum 对象来处理