【daily】Java枚舉 - fastjson對enum的處理

目的

  一、枚舉值轉換成徹底的json;java

  二、對象中的枚舉成員徹底轉換成json;json

  三、枚舉類的所有值轉換成json;api

 

枚舉定義

public enum SongsEnum {
     SAFE_AND_SOUND(1,"Taylor Swift","Safe&Sound","2011-12-26")
    ,SHAKE_IT_OFF(2,"Taylor Swift","Shake It Off","2014-08-19")
    ,STYLE(3,"Taylor Swift","Style","2015-02-09")
    ,SOUND_OF_SILENCE(4,"Simon & Garfunkel","The Sound Of Silence","1966-01-17")
    ,BETTER_MAN(5,"Little Big Town","Better Man","2016-10-20")
    ,YESTERDAY_ONCE_MORE(6,"Carpenters","Yesterday Once More","1973-05-16")
    ;

    public final int index;
    public final String singer;
    public final String name;
    public final String date;

    private SongsEnum(int seq, String singer, String name, String date) {
        this.index = seq;
        this.singer = singer;
        this.name = name;
        this.date = date;
    }

    public int getIndex() { return index; }

    public String getSinger() { return singer; }

    public String getName() { return name; }

    public String getDate() { return date; }
}

 

1、枚舉值轉換json

指望結果:SongsEnum.SAFE_AND_SOUND -> {"date":"2011-12-26","index":1,"name":"Safe&Sound","singer":"Taylor Swift"}數組

## 默認調用結果
JSON.toJSONString(SongsEnum.SAFE_AND_SOUND) -> "SAFE_AND_SOUND"

JSON.toJSONString(SongsEnum.BETTER_MAN,SerializerFeature.WriteEnumUsingName) -> "BETTER_MAN"

JSON.toJSONString(SongsEnum.STYLE,SerializerFeature.WriteEnumUsingToString) -> "STYLE"

默認的使用fastjson轉換enum,那麼獲得的enum json可能不是想要的.ide

(1) 重寫enum的toString()測試

@Override
public String toString() {
  return "{'name':"+this.name+",'singer':"+this.singer+"}";
}

JSON.toJSONString(SongsEnum.STYLE,SerializerFeature.WriteEnumUsingToString) -> "{'name':Style,'singer':Taylor Swift}"

  雖然能夠這樣獲得想要的結果,但相對來講太麻煩,每一個enum類都要從寫toString().ui

  (此種方式並無研究,因此以上重寫toString()的代碼可能存在問題)this

(2) fastjson的config設置spa

  fastjson提供的JSON.toJSONString(...)有不少重載的方法,例如:debug

    public static String toJSONString(Object object, SerializeConfig config, SerializerFeature... features)

  因此,fastjson能夠經過設置SerializeConfig來配置enum的序列化。

public static void main(String[] args) {
    SerializeConfig config = new SerializeConfig();
    config.configEnumAsJavaBean(SongsEnum.class);
    String s = JSON.toJSONString(SongsEnum.SOUND_OF_SILENCE, config);
    System.out.println(s);
    // {"date":"1966-01-17","index":4,"name":"The Sound Of Silence","singer":"Simon & Garfunkel"}
}

2、對象中的枚舉成員徹底轉換成json

public enum StatusEnum {
    STATUS_A(0,"狀態A"),
    STATUS_B(1,"狀態B"),
    STATUS_C(2,"狀態C");

    public final int index;
    public final String status;

    StatusEnum(int i, String status) {
        this.index = i;
        this.status = status;
    }

    public int getIndex() { return index; }

    public String getStatus() { return status; }
}
class JavaBean{
    private String name;

    private SongsEnum song;

    private StatusEnum status;

    public JavaBean(String name,SongsEnum song,StatusEnum status){
        this.name = name;
        this.song = song;
        this.status = status;
    }

    //省略setter/getter
}

指望: {"name":"vegilyn","song":{"date":"2014-08-19","index":2,"name":"Shake It Off","singer":"Taylor Swift"},"status":{"index":1,"status":"狀態B"}}

默認結果:

JSON.toJSONString(new JavaBean("vegilyn",SongsEnum.SHAKE_IT_OFF,StatusEnum.STATUS_B)) -> {"name":"vegilyn","song":"SHAKE_IT_OFF","status":"STATUS_B"}

能夠看出此結果和目的1中的結果同樣的,因此經過目的1的方式也能夠解決。

SerializeConfig config = new SerializeConfig();
config.configEnumAsJavaBean(SongsEnum.class); // 配置enum轉換
String s = JSON.toJSONString(new JavaBean("vegilyn",SongsEnum.SHAKE_IT_OFF,StatusEnum.STATUS_B),config);
System.out.println(s);
// {"name":"vegilyn","song":{"date":"2014-08-19","index":2,"name":"Shake It Off","singer":"Taylor Swift"},"status":"STATUS_B"}
SerializeConfig config = new SerializeConfig();
config.configEnumAsJavaBean(SongsEnum.class,StatusEnum.class); // 配置enum轉換 
String s = JSON.toJSONString(new JavaBean("vegilyn",SongsEnum.SHAKE_IT_OFF,StatusEnum.STATUS_B),config);
System.out.println(s);
// {"name":"vegilyn","song":{"date":"2014-08-19","index":2,"name":"Shake It Off","singer":"Taylor Swift"},"status":{"index":1,"status":"狀態B"}}

特別枚舉數組轉換的json:(注意重複值的json值)

public static void main(String[] args) {
    SongsEnum[] songsEnums = {SongsEnum.BETTER_MAN, SongsEnum.SAFE_AND_SOUND, SongsEnum.BETTER_MAN};
    SerializeConfig config = new SerializeConfig();
    config.configEnumAsJavaBean(SongsEnum.class);

    System.out.println(JSON.toJSONString(songsEnums,config));
}

// [{"date":"2016-10-20","index":5,"name":"Better Man","singer":"Little Big Town"},{"date":"2011-12-26","index":1,"name":"Safe&Sound","singer":"Taylor Swift"},{"$ref":"$[0]"}]

 

3、枚舉類的所有值轉換成json

指望結果:

[
    {"index": 1, "singer": "Taylor Swift", "name": "Safe&Sound", "date": "2011-12-26"},
    {"index": 2, "singer": "Taylor Swift", "name": "Shake It Off", "date": "2014-08-19"},
    {"index": 3, "singer": "Taylor Swift", "name": "Style", "date": "2015-02-09"},
    {"index": 4, "singer": "Simon & Garfunkel", "name": "The Sound Of Silence", "date": "1966-01-17"},
    {"index": 5, "singer": "Little Big Town", "name": "Better Man", "date": "2016-10-20"},
    {"index": 6, "singer": "Carpenters", "name": "Yesterday Once More", "date": "1973-05-16"}
]

實現代碼:

 public static void main(String[] args) {
        SongsEnum[] values = SongsEnum.values();
        List<SongsEnum> songsEnums = new ArrayList<SongsEnum>();
        for (SongsEnum value : values) {
            songsEnums.add(value);
        }

        SerializeConfig config = new SerializeConfig();
        config.configEnumAsJavaBean(SongsEnum.class);

        System.out.println(JSON.toJSONString(songsEnums, config));
    }

以上能達到想要的效果,可是,每一個enum類都要重複寫以上代碼。因此,利用反射來寫一個公共的方法。

 public static String toJson(Class<? extends Enum> enumClass) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method methodValues = enumClass.getMethod("values");
        Object invoke = methodValues.invoke(null);
        int length = java.lang.reflect.Array.getLength(invoke);
        List<Object> values = new ArrayList<Object>();
        for (int i=0; i<length; i++) {
            values.add(java.lang.reflect.Array.get(invoke, i));
        }

        SerializeConfig config = new SerializeConfig();
        config.configEnumAsJavaBean(enumClass);

        return JSON.toJSONString(values,config);
    }
public static void main(String[] args) {
        try {
            System.out.println(EnumJsonUtil.toJson(StatusEnum.class));
            // [{"index":0,"status":"狀態A"},{"index":1,"status":"狀態B"},{"index":2,"status":"狀態C"}]
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

 

4、擴展:fastjson如何獲得enum的field定義。

經過jdk反射機制獲得的field。

public static void main(String[] args) {
  Field[] fields = SongsEnum.class.getFields();
  for (Field field : fields) {
    System.out.println(field.getName());
  }
}

SAFE_AND_SOUND
SHAKE_IT_OFF
STYLE
SOUND_OF_SILENCE
BETTER_MAN
YESTERDAY_ONCE_MORE
index
singer
name
date

如上,若是是經過jdk反射機制獲得class的filed。會發現獲得了不想要的結果,ex: SAFE_AND_SOUND 、SHAKE_IT_OFF 等。

想獲得的field其實只想要: index、singer、name、date。

固然也能夠本身分析,而後過濾出本身想要的class的field。

但下面看下fastjson中怎麼獲得enum中指望的field。(fastjson版本:1.2.31)

(1) 跟蹤代碼:  new SerializeConfig().configEnumAsJavaBean(enumClass)

public void configEnumAsJavaBean(Class<? extends Enum>... enumClasses) {
        for (Class<? extends Enum> enumClass : enumClasses) {
            put(enumClass, createJavaBeanSerializer(enumClass));
        }
    }
 private final ObjectSerializer createJavaBeanSerializer(Class<?> clazz) {
        SerializeBeanInfo beanInfo = TypeUtils.buildBeanInfo(clazz, null, propertyNamingStrategy, fieldBase);
        if (beanInfo.fields.length == 0 && Iterable.class.isAssignableFrom(clazz)) {
            return MiscCodec.instance;
        }

        return createJavaBeanSerializer(beanInfo);
    }
public class SerializeBeanInfo {

    protected final Class<?> beanType;
    protected final String   typeName;
    protected final JSONType jsonType;

    protected final FieldInfo[] fields;
    protected final FieldInfo[] sortedFields;

    protected int               features;

    public SerializeBeanInfo(Class<?> beanType, //
                             JSONType jsonType, //
                             String typeName, //
                             int features,
                             FieldInfo[] fields, //
                             FieldInfo[] sortedFields
                             ){
        this.beanType = beanType;
        this.jsonType = jsonType;
        this.typeName = typeName;
        this.features = features;
        this.fields = fields;
        this.sortedFields = sortedFields;
    }

}

能夠看到SerializeBeanInfo 中定義的有 fields 。

(很好奇爲何定義成 protected , TyoeUtils.buildBeanInfo(...)是public的,返回的SerializeBeanInfo 也是public。

可是,SerializeBeanInfo. fields倒是protected 的。

致使我知道fastjson中有這麼一個util方法,能夠獲得我想要的enum信息(更確切的是能夠獲得class的信息)。

可是,最後我並不能直接在個人代碼中使用SerializeBeanInfo。)。

經過debug能夠看到SerializeBeanInfo對象的屬性,就是我想要的結果

image

接着進去看TyoeUtils.buildBeanInfo(...)的實現:

   public static SerializeBeanInfo buildBeanInfo(Class<?> beanType //
            , Map<String, String> aliasMap //
            , PropertyNamingStrategy propertyNamingStrategy //
            , boolean fieldBased //
    ) {

        JSONType jsonType = beanType.getAnnotation(JSONType.class);

        // fieldName,field ,先生成fieldName的快照,減小以後的findField的輪詢
         Map<String, Field> fieldCacheMap = new HashMap<String, Field>();
        ParserConfig.parserAllFieldToCache(beanType, fieldCacheMap);

        List<FieldInfo> fieldInfoList = fieldBased
                ? computeGettersWithFieldBase(beanType, aliasMap, false, propertyNamingStrategy) //
                : computeGetters(beanType, jsonType, aliasMap, fieldCacheMap, false, propertyNamingStrategy);

    // 省略...

}

執行computeGetters(...)的結果:

image

 public static List<FieldInfo> computeGetters(Class<?> clazz, //
                                                 JSONType jsonType, //
                                                 Map<String, String> aliasMap, //
                                                 Map<String, Field> fieldCacheMap, //
                                                 boolean sorted, //
                                                 PropertyNamingStrategy propertyNamingStrategy //
    ) {
        Map<String, FieldInfo> fieldInfoMap = new LinkedHashMap<String, FieldInfo>();

        for (Method method : clazz.getMethods()) {
            String methodName = method.getName();
            int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
            String label = null;

            if (Modifier.isStatic(method.getModifiers())) { continue; }  // 跳過static method

            if (method.getReturnType().equals(Void.TYPE)) { continue; }

            if (method.getParameterTypes().length != 0) { continue; }

            if (method.getReturnType() == ClassLoader.class) { continue; }

            if (method.getName().equals("getMetaClass")
                && method.getReturnType().getName().equals("groovy.lang.MetaClass")) {
                continue;
            }

            JSONField annotation = method.getAnnotation(JSONField.class);

            if (annotation == null) {
                annotation = getSuperMethodAnnotation(clazz, method);
            }

            if (annotation != null) {
                if (!annotation.serialize()) { continue; }

                ordinal = annotation.ordinal();
                serialzeFeatures = SerializerFeature.of(annotation.serialzeFeatures());
                parserFeatures = Feature.of(annotation.parseFeatures());

                if (annotation.name().length() != 0) {
                    String propertyName = annotation.name();

                    if (aliasMap != null) {
                        propertyName = aliasMap.get(propertyName);
                        if (propertyName == null) {
                            continue;
                        }
                    }

                    FieldInfo fieldInfo = new FieldInfo(propertyName, method, null, clazz, null, ordinal,
                                                        serialzeFeatures, parserFeatures, annotation, null, label);
                    fieldInfoMap.put(propertyName, fieldInfo);
                    continue;
                }

                if (annotation.label().length() != 0) {
                    label = annotation.label();
                }
            }

            if (methodName.startsWith("get")) {
                if (methodName.length() < 4) { continue; }

                if (methodName.equals("getClass")) { continue;}

                if (methodName.equals("getDeclaringClass") && clazz.isEnum()) { continue; }

                char c3 = methodName.charAt(3);

                String propertyName;
                if (Character.isUpperCase(c3) //
                    || c3 > 512 // for unicode method name
                ) {
                   if (compatibleWithJavaBean) {
                        propertyName = decapitalize(methodName.substring(3));
                    } else {
                        propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
                    }
                    propertyName = getPropertyNameByCompatibleFieldName(fieldCacheMap, methodName,  propertyName,3);
                } else if (c3 == '_') {
                    propertyName = methodName.substring(4);
                } else if (c3 == 'f') {
                    propertyName = methodName.substring(3);
                } else if (methodName.length() >= 5 && Character.isUpperCase(methodName.charAt(4))) {
                    propertyName = decapitalize(methodName.substring(3));
                } else {
                    continue;
                }

                boolean ignore = isJSONTypeIgnore(clazz, propertyName);

                if (ignore) {
                    continue;
                }
                //假如bean的field不少的狀況一下,輪詢時將大大下降效率
                Field field = ParserConfig.getFieldFromCache(propertyName, fieldCacheMap);

                if (field == null && propertyName.length() > 1) {
                    char ch = propertyName.charAt(1);
                    if (ch >= 'A' && ch <= 'Z') {
                        String javaBeanCompatiblePropertyName = decapitalize(methodName.substring(3));
                        field = ParserConfig.getFieldFromCache(javaBeanCompatiblePropertyName, fieldCacheMap);
                    }
                }

                JSONField fieldAnnotation = null;
                if (field != null) {
                    fieldAnnotation = field.getAnnotation(JSONField.class);

                    if (fieldAnnotation != null) {
                        if (!fieldAnnotation.serialize()) {
                            continue;
                        }

                        ordinal = fieldAnnotation.ordinal();
                        serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                        parserFeatures = Feature.of(fieldAnnotation.parseFeatures());

                        if (fieldAnnotation.name().length() != 0) {
                            propertyName = fieldAnnotation.name();

                            if (aliasMap != null) {
                                propertyName = aliasMap.get(propertyName);
                                if (propertyName == null) {
                                    continue;
                                }
                            }
                        }

                        if (fieldAnnotation.label().length() != 0) {
                            label = fieldAnnotation.label();
                        }
                    }
                }

                if (aliasMap != null) {
                    propertyName = aliasMap.get(propertyName);
                    if (propertyName == null) {
                        continue;
                    }
                }

                if (propertyNamingStrategy != null) {
                    propertyName = propertyNamingStrategy.translate(propertyName);
                }

                FieldInfo fieldInfo = new FieldInfo(propertyName, method, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
                                                    annotation, fieldAnnotation, label);
                fieldInfoMap.put(propertyName, fieldInfo);
            }

            if (methodName.startsWith("is")) {
                if (methodName.length() < 3) {
                    continue;
                }

                if (method.getReturnType() != Boolean.TYPE
                        && method.getReturnType() != Boolean.class) {
                    continue;
                }

                char c2 = methodName.charAt(2);

                String propertyName;
                if (Character.isUpperCase(c2)) {
                    if (compatibleWithJavaBean) {
                        propertyName = decapitalize(methodName.substring(2));
                    } else {
                        propertyName = Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
                    }
                    propertyName = getPropertyNameByCompatibleFieldName(fieldCacheMap, methodName,  propertyName,2);
                } else if (c2 == '_') {
                    propertyName = methodName.substring(3);
                } else if (c2 == 'f') {
                    propertyName = methodName.substring(2);
                } else {
                    continue;
                }

                Field field = ParserConfig.getFieldFromCache(propertyName,fieldCacheMap);

                if (field == null) {
                    field = ParserConfig.getFieldFromCache(methodName,fieldCacheMap);
                }

                JSONField fieldAnnotation = null;
                if (field != null) {
                    fieldAnnotation = field.getAnnotation(JSONField.class);

                    if (fieldAnnotation != null) {
                        if (!fieldAnnotation.serialize()) {
                            continue;
                        }

                        ordinal = fieldAnnotation.ordinal();
                        serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                        parserFeatures = Feature.of(fieldAnnotation.parseFeatures());

                        if (fieldAnnotation.name().length() != 0) {
                            propertyName = fieldAnnotation.name();

                            if (aliasMap != null) {
                                propertyName = aliasMap.get(propertyName);
                                if (propertyName == null) {
                                    continue;
                                }
                            }
                        }

                        if (fieldAnnotation.label().length() != 0) {
                            label = fieldAnnotation.label();
                        }
                    }
                }

                if (aliasMap != null) {
                    propertyName = aliasMap.get(propertyName);
                    if (propertyName == null) {
                        continue;
                    }
                }

                if (propertyNamingStrategy != null) {
                    propertyName = propertyNamingStrategy.translate(propertyName);
                }

                //優先選擇get
                if (fieldInfoMap.containsKey(propertyName)) {
                    continue;
                }

                FieldInfo fieldInfo = new FieldInfo(propertyName, method, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
                                                    annotation, fieldAnnotation, label);
                fieldInfoMap.put(propertyName, fieldInfo);
            }
        } // for methods end
        // for methods : 優先經過定義的method來獲取FieldInfo。
        // 而後,才經過Fields獲得沒有定義相似getXXX的field。
        Field[] fields = clazz.getFields();
        computeFields(clazz, aliasMap, propertyNamingStrategy, fieldInfoMap, fields);

        return getFieldInfos(clazz, sorted, fieldInfoMap);
    }

若是枚舉中的: index、singer、name、date。沒有定義getter方法,那麼fieldInfoMap在for methods end以後是empty的。

而且通過測試發現, 若是class中定義了:  public final static String testName = "test";
只有提供了 : public String getTestName(){return testName;} 纔會被fastjson轉換。注:不能是public static方法。(緣由看上面的源碼)

或者 定義成 public final String testName = "test"; (但明顯沒有static的含義所在,緣由看下面源碼)

 

private static void computeFields(
            Class<?> clazz, //
            Map<String, String> aliasMap, //
            PropertyNamingStrategy propertyNamingStrategy, //
            Map<String, FieldInfo> fieldInfoMap, //
            Field[] fields) {

        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())) { // 若是是static 修飾的field 跳過。
                continue;
            }

            JSONField fieldAnnotation = field.getAnnotation(JSONField.class); // fastjson提供的註解

            int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
            String propertyName = field.getName();
            String label = null;
            if (fieldAnnotation != null) {
                if (!fieldAnnotation.serialize()) {
                    continue;
                }

                ordinal = fieldAnnotation.ordinal();
                serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                parserFeatures = Feature.of(fieldAnnotation.parseFeatures());

                if (fieldAnnotation.name().length() != 0) {
                    propertyName = fieldAnnotation.name();
                }

                if (fieldAnnotation.label().length() != 0) {
                    label = fieldAnnotation.label();
                }
            }

            if (aliasMap != null) { // 別名定義
                propertyName = aliasMap.get(propertyName);
                if (propertyName == null) {
                    continue;
                }
            }

            if (propertyNamingStrategy != null) {
                propertyName = propertyNamingStrategy.translate(propertyName);
            }

            if (!fieldInfoMap.containsKey(propertyName)) {// map中不存在該field的FieldInfo, 則建立一個
                FieldInfo fieldInfo = new FieldInfo(propertyName, null, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
                                                    null, fieldAnnotation, label);
                fieldInfoMap.put(propertyName, fieldInfo);
            }
        }
    }

因此,根據枚舉的特性。ex: SAFE_AND_SOUND 、SHAKE_IT_OFF 等。在enum中的定義都是 public static final ...

image

而且在methods中也不存在getXXX。

若是,我在enum中定義一個method: getSAFE_AND_SOUND(),則field中會出現。

image

相關文章
相關標籤/搜索