Jackson 通過自定義注解來控制json key的格式


Jackson 通過自定義注解來控制json key的格式

最近我這邊有一個需求就是需要把Bean中的某一些特殊字段的值進行替換。而這個替換過程是需要依賴一個第三方的dubbo服務的。為了使得這個轉換功能更加的通用,我們采用了下面的方式:

  • client端使用自定義的注解(假設為@Dimension)標記Bean中所有的「特殊字段」
  • client端把bean轉換為json格式,但是這個轉換過程的要求是:這些特殊的字段對應的json的key需要符合一定的格式,而這個格式依賴於標記的@Dimension注解
  • 然后client端通過dubbo RPC服務把json扔給server端,server進行一些json解析,替換之后把替換之后的json扔給client端,然后client端把接收到的json再轉回為之前的Bean對象的實例。

我們先來看看把bean轉為json,一般沒有特殊要求的話,我們都是:

 /**
     * Object可以是POJO,也可以是Collection或數組。
     * 如果對象為Null, 返回"null".
     * 如果集合為空集合, 返回"[]".
     *
     * @param object the object to json
     * @return toJson result
     */
    public String toJson(Object object) {
        try {
            return mapper.writeValueAsString(object);
        } catch (IOException e) {
            LOGGER.error("write to json string error:" + object, e);
            return null;
        }
    }

這種是默認的情況,生成的json的key和對應的Bean的filed的name是一模一樣的。

而Jackson也給我們提供了注解:@JsonProperty注解來幫助我們重命名生成的json的key。但是他這個重命名並不是很靈活,因為他只能固定的重命名為某一個「確定的」值,而不能容許我們做一些額外的操作。

所以在這種情況下,我打算自定義一個注解,因為業務場景相關,我們的注解定義如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Dimension {
    String valueType();
}

假設我們的json的key的生成規則如下:

  • valueType()的值為“id”時,json key追加后綴“_id”
  • valueType()的值為"code"時,json key追加后綴“_code”

這個時候我們就可以使用Jackson提供給我們強大的JacksonAnnotationIntrospector類了。

import com.google.common.base.Preconditions;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.Versioned;
import org.codehaus.jackson.map.introspect.AnnotatedField;
import org.codehaus.jackson.map.introspect.JacksonAnnotationIntrospector;
import org.codehaus.jackson.util.VersionUtil;

import java.lang.annotation.Annotation;

import static com.google.common.base.Strings.isNullOrEmpty;

/**
 * @author rollenholt
 */
public class DimensionFieldSerializer extends JacksonAnnotationIntrospector implements Versioned {

    @Override
    public Version version() {
        return VersionUtil.versionFor(getClass());
    }


    @Override
    public boolean isHandled(Annotation ann) {
        Class<?> cls = ann.annotationType();
        if (Dimension.class == cls) {
            return true;
        }
        return super.isHandled(ann);
    }


    @Override
    public String findSerializablePropertyName(AnnotatedField af) {
        return getPropertyName(af);
    }

    @Override
    public String findDeserializablePropertyName(AnnotatedField af) {
        return getPropertyName(af);
    }

    private String getPropertyName(AnnotatedField af) {
        Dimension annotation = af.getAnnotation(Dimension.class);
        if (annotation != null) {
            String valueType = annotation.valueType();
            Preconditions.checkArgument(!isNullOrEmpty(valueType), "@Dimension注解中的valudType不能為空");
            if (valueType.equalsIgnoreCase("id")) {
                return af.getName() + "_id";
            }
            if (valueType.equalsIgnoreCase("code")) {
                return af.getName() + "_code";
            }
        }
        return af.getName();
    }

}


同時為了觸發上面的代碼,以及為了驗證我們的功能,我們有如下的代碼:

/**
 * @author rollenholt
 */
public class DimensionAdapterHelper {

    private final static ObjectMapper objectMapper = new ObjectMapper();

    static {
        AnnotationIntrospector dimensionFieldSerializer = new DimensionFieldSerializer();
        objectMapper.setAnnotationIntrospector(dimensionFieldSerializer);
    }

    public static String beanToJson(Object object) {
        StringWriter sw = new StringWriter();
        try {
            objectMapper.writeValue(sw, object);
            return sw.toString();
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    public static <T> T jsonToBean(String json, Class<T> clazz) {
        try {
            return (T) objectMapper.readValue(json, clazz);
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }


    public static class Type {
        private String code;

        @Dimension(valueType = "id")
        private String description;

        @Dimension(valueType = "code")
        private String value;

        public Type() {
        }

        public Type(String code, String description, String value) {
            super();
            this.code = code;
            this.description = description;
            this.value = value;
        }

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this);
        }
    }

    public static void main(String[] args) {
        Type t = new Type("a", "b", "c");
        String json = beanToJson(t);
        System.out.println(json);
        Type type = jsonToBean(json, Type.class);
        System.out.println(type);

    }


}

運行之后輸出結果為:

{"code":"a","description_id":"b","value_code":"c"}
DimensionAdapterHelper$Type@2cb4c3ab[code=a,description=b,value=c]

還算是很符合我們的期望的。

至於server端是如何替換json字符串的key的那塊,簡單的說一下,因為key有一定的格式,所以可以遞歸遍歷json的所有key,就可以拿到有哪些key-value對需要處理了。關於如何在Java中遞歸便利Json,這個比較簡單。如果大家覺的有需要,我后面在寫。

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM