【问题标题】:From Spring BindingResult to field JSONPath/JSON Pointer, with Jackson从 Spring BindingResult 到字段 JSONPath/JSON Pointer,与 Jackson
【发布时间】:2020-08-04 13:57:02
【问题描述】:

我有一个使用 javax.validation 注释的 Spring Boot 应用程序,我试图返回指向违规字段的友好 JSON 错误消息,但是从可用的“Java-object”路径转换为 ​​JSONPath 或 JSON 指针是什么我没有找到办法。

SSCO 示例:

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;

import javax.validation.Valid;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.Min;
import java.util.List;

public class Test {

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);

        Data data = new Data();
        System.out.println("Serialized: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(data));

        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        validator.validate(data).forEach(violation -> {
            System.out.println("Path: " + violation.getPropertyPath());
        });
    }

    public static class Data {
        @JsonProperty("foobar")
        @Valid
        public List<Foo> foo = List.of(new Foo());
    }
    public static class Foo {
        @Min(100)
        public int barBaz = 42;
    }

}

输出:

Serialized: {
  "foobar" : [ {
    "bar_baz" : 42
  } ]
}
Path: foo[0].barBaz

如您所见,我需要将foo[0].barBaz 转换为$.foobar[0].bar_baz/foobar/0/bar_baz。解析后的对象(上面的data 变量)也由保存验证信息的BindingResult 对象提供。

我想过做一些字符串操作,但那很麻烦,很麻烦,而且很容易被 @JsonProperty 打破,我需要单独处理,也许还有其他我没有考虑过的极端情况。另外,我们以SNAKE_CASE 为标准,为了简化任务而进行更改不是解决方案。

我想 Jackson 的 ObjectMapper 可以以某种方式用于进行这种转换,或者其他一些 Jackson API,但我找不到任何相关信息。任何其他可以做到这一点的库也很好(理想情况下,它应该理解 Jackson 注释,如 @JsonProperty)。

【问题讨论】:

    标签: java spring jackson jackson-databind spring-validator


    【解决方案1】:

    您可以使用 Hibernate Validator 6.1.5 轻松完成。

    您需要提供自己的PropertyNodeNameProvider 实现。

    通过实现它,我们可以定义在验证期间如何解析属性名称。在我们的例子中,我们想从 Jackson 配置中读取值。

    创建验证器:

     ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
            .configure()
            .propertyNodeNameProvider(new JacksonPropertyNodeNameProvider())
            .buildValidatorFactory();
    

    JacksonPropertyNodeNameProvider:

    public class JacksonPropertyNodeNameProvider implements PropertyNodeNameProvider {
    
      private final ObjectMapper objectMapper = new ObjectMapper();
    
      @Override
      public String getName(Property property) {
        if ( property instanceof JavaBeanProperty ) {
            return getJavaBeanPropertyName( (JavaBeanProperty) property );
        }
    
        return getDefaultName( property );
      }
    
      private String getJavaBeanPropertyName(JavaBeanProperty property) {
        JavaType type = objectMapper.constructType( property.getDeclaringClass() );
        BeanDescription desc = objectMapper.getSerializationConfig().introspect( type );
    
        return desc.findProperties()
                .stream()
                .filter( prop -> prop.getInternalName().equals( property.getName() ) )
                .map( BeanPropertyDefinition::getName )
                .findFirst()
                .orElse( property.getName() );
      }
    
      private String getDefaultName(Property property) {
        return property.getName();
      }
    }
    

    更多详情您可以在documentation:

    【讨论】:

    • 谢谢,您的代码运行良好。对于这样的任务,我希望有这个开箱即用的,但它可以很容易地构建。
    【解决方案2】:

    据我了解您的问题,您正在寻找通往您的领域的道路。由于没有 I/P JSON,我举了一个例子。

    package jsonpath;
    
    import java.util.List;
    
    import com.jayway.jsonpath.Configuration;
    import com.jayway.jsonpath.Option;
    import static com.jayway.jsonpath.JsonPath.*;
    
    public class GetPaths {
        
        public static void main(String [] args) {
            String json = "{\"top_field\": { \"mid_field\": [ { \"my_field\": true, }, { \"my_field\": true, } ], \"another_mid_field\": [ { \"my_field\": false } ] }}";
            
            Configuration conf = Configuration.builder().options(Option.AS_PATH_LIST).build();
            List<String> pathList = using(conf).parse(json).read("$..my_field");
            for(String path : pathList) {
                System.out.println(path);
            }
        }
    }
    

    会准确输出

    $['top_field']['mid_field'][0]['my_field']
    $['top_field']['mid_field'][1]['my_field']
    $['top_field']['another_mid_field'][0]['my_field']
    

    如果您对此进行一些简单的字符串替换,我认为这是一个很好且简单的解决方案。我不确定您是否可以使用普通的 Jackson/FasterXML 获得类似的东西。 JsonPath 在后台使用 Jackson。

    您可以在 official git repo 上获取有关 Jayway JsonPath 的更多信息

    【讨论】:

    • 我猜 OP 没有 json。相反,他有Data java 对象。当验证器失败时,他有一个 java 属性路径,但考虑到 Data java 对象中的注释,他想将其转换为 json 路径
    • 我怀疑。他正在抛出JsonProcessingException 表示有 JSON。休息问题可以改进。有很多理论......感谢输入@Kavithakaran Kanapathippillai
    【解决方案3】:
    1. 以@Lukasz 的answer 为基础,它将以蛇形大小写或任何您想要的格式为您提供属性名称,但在您提供@JsonProperty() 时提供。

      因此,使用JacksonPropertyNodeNameProvider @JsonProperty("bar_baz") 添加到public int barBaz = 42; AND 将得到以下输出。

      路径:foobar[0].bar_baz

    1. 要将foobar[0].bar_baz 转换为jsonpath,我认为字符串操作就足够了。
       validator.validate(data).forEach(violation -> {
         System.out.println("Path: " + "$." + violation.getPropertyPath());
       });
    

    最终的输出是

    路径:$.foobar[0].bar_baz

    【讨论】:

      猜你喜欢
      • 2016-03-10
      • 2015-03-05
      • 2012-07-04
      • 1970-01-01
      • 1970-01-01
      • 2020-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多