美文网首页
请求类中的json字符串字段如何自动转换(@RequestBod

请求类中的json字符串字段如何自动转换(@RequestBod

作者: uhfun | 来源:发表于2022-09-25 21:40 被阅读0次

    该文章为原创(转载请注明出处):请求类中的json字符串字段如何自动转换(@RequestBody、@ModelAttribute) - 简书 (jianshu.com)

    使用场景

    前端传值:

    {
        "userJson": "{\"phone\":\"123\",\"name\":\"xxx\"}"
    }
    

    为了支持前端的数据结构,后端做了妥协,没法使用对自己更方便更合理的结构?

    后端@RequestBody注解的类中的字段为 userJson 传输,需要在controller层手动处理

    @GetMapping("test.do")
    public RestResponse<Void> test(@RequestBody TestBody body) {
        body.setUser(JSON.parseObject(body.getUserJson(), User.class));
        // service.handleUser(body);
    }
    
    @Data
    static class TestBody {
        private String userJson;
        private User user;
    }
    static class User {
        private String phone;
        private String name;
    }
    

    如何把逻辑提取到框架或者工具层面,实现自动装配,无感使用对象 ?

    1. 扩展Jackson的反序列化器,转换String到字段类型的对象

    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.databind.*;
    import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
    import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
     
    import java.io.IOException;
     
    /**
     * @author uhfun
     * @see JsonDeserialize#using()
     */
    public class JacksonStringToJavaObjectDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer {
        private JavaType javaType;
     
        @Override
        public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            ObjectMapper mapper = (ObjectMapper) p.getCodec();
            String value = p.getValueAsString();
            return value == null ? null : mapper.readValue(value, javaType.getRawClass());
        }
     
        @Override
        public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
            javaType = property.getType();
            return this;
        }
    }
    

    2. 扩展FastJson的反序列化器,转换String到字段类型的对象

    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.annotation.JSONField;
    import com.alibaba.fastjson.parser.DefaultJSONParser;
    import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
     
    import java.lang.reflect.Type;
     
    import static com.alibaba.fastjson.parser.JSONToken.LITERAL_STRING;
     
    /**
     * @author uhfun
     * @see JSONField#deserializeUsing()
     */
    public class FastJsonStringToJavaObjectDeserializer implements ObjectDeserializer {
        @Override
        public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
            String s = parser.parseObject(String.class);
            return JSON.parseObject(s, type);
        }
     
        @Override
        public int getFastMatchToken() {
            return LITERAL_STRING;
        }
    }
    

    如何使用扩展的反序列化器

    1. Jackson

    @Data
    static class TestBody {
        @JsonDeserialize(using = JacksonStringToJavaObjectDeserializer.class)
        private User userJson;
    }
    

    2. FastJson

    @Data
    static class TestBody {
        @JSONField(deserializeUsing = FastJsonStringToJavaObjectDeserializer.class)
        private User userJson;
    }
    

    @ModelAttribute如何支持自动绑定json字符串的字段 为 对象 ?

    @GetMapping("test.do")
    public RestResponse<Void> test(@ModelAttribute TestBody body) {
        body.setUser(JSON.parseObject(body.getUserJson(), User.class));
        // service.handleUser(body);
    }
    @Data
    static class TestBody {
        private String userJson;
        private User user;
    }
    static class User {
        private String phone;
        private String name;
    }
    

    实现下面的切面,自动注册需要转换的转换器

    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.annotation.JSONField;
    import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
    import com.google.common.collect.Sets;
    import org.springframework.boot.autoconfigure.web.format.WebConversionService;
    import org.springframework.core.annotation.AnnotatedElementUtils;
    import org.springframework.core.annotation.AnnotationUtils;
    import org.springframework.core.convert.TypeDescriptor;
    import org.springframework.core.convert.converter.GenericConverter;
    import org.springframework.web.bind.WebDataBinder;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.InitBinder;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
     
    import java.lang.reflect.Field;
    import java.util.Set;
     
    import static java.util.Collections.singleton;
     
    /**
     * 使@ModelAttribute支持绑定Json字符串为对象
     *
     * @author uhfun
     * @see RequestMappingHandlerAdapter#initControllerAdviceCache()
     * 寻找@ControllerAdvice切面下@InitBinder注解的方法
     * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#INIT_BINDER_METHODS
     * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDataBinderFactory(org.springframework.web.method.HandlerMethod)
     * 根据切面构建InitBinderMethod方法
     * @see org.springframework.web.method.annotation.InitBinderDataBinderFactory#initBinder
     * 初始化binder时反射调用
     * @see ModelAttribute
     */
    @ControllerAdvice
    public class StringToJsonObjectSupport {
        private static final Set<Class<?>> CONVERTER_INITIALIZED = Sets.newConcurrentHashSet();
     
        @InitBinder
        public void initStringJsonObjectConverter(WebDataBinder dataBinder) {
            Object target = dataBinder.getTarget();
            WebConversionService conversionService;
            if (dataBinder.getConversionService() instanceof WebConversionService) {
                conversionService = (WebConversionService) dataBinder.getConversionService();
                if (target != null && !CONVERTER_INITIALIZED.contains(target.getClass())) {
                    for (Field field : dataBinder.getTarget().getClass().getDeclaredFields()) {
                        JSONField jsonField = AnnotationUtils.findAnnotation(field, JSONField.class);
                        boolean jsonFieldAnnotated = jsonField != null && jsonField.deserialize(),
                                deserializeAnnotated = jsonFieldAnnotated || AnnotatedElementUtils.hasAnnotation(field, JsonDeserialize.class);
                        if (deserializeAnnotated) {
                            Class<?> type = field.getType();
                            conversionService.addConverter(new JsonStringToObjectConverter(type));
                        }
                    }
                    CONVERTER_INITIALIZED.add(target.getClass());
                }
            } else {
                throw new IllegalStateException("dataBinder的ConversionService不是WebConversionService类型实例");
            }
        }
     
        static class JsonStringToObjectConverter implements GenericConverter {
     
            private final Class<?> targetType;
     
            public JsonStringToObjectConverter(Class<?> targetType) {
                this.targetType = targetType;
            }
     
            @Override
            public Set<ConvertiblePair> getConvertibleTypes() {
                return singleton(new ConvertiblePair(String.class, targetType));
            }
     
            @Override
            public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
                return JSON.parseObject((String) source, targetType.getType());
            }
        }
    }
    
    @Data
    static class TestBody {
        @JsonDeserialize
        private User userJson;
    }
    

    该文章为原创(转载请注明出处):请求类中的json字符串字段如何自动转换(@RequestBody、@ModelAttribute) - 简书 (jianshu.com)

    相关文章

      网友评论

          本文标题:请求类中的json字符串字段如何自动转换(@RequestBod

          本文链接:https://www.haomeiwen.com/subject/zwmxartx.html