从 Spring BindingResult 到字段 JSONPath/JSON 指针,与 Jackson

From Spring BindingResult to field JSONPath/JSON Pointer, with Jackson

我有一个使用 javax.validation 注释的 Spring 启动应用程序,我正在尝试 return 友好 JSON 错误消息指向有问题的字段,但从指向 JSONPath 或 JSON Pointer 的可用“Java-object”路径是我找不到的方法。

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 可以以某种方式用于进行此转换,但我找不到任何相关信息。任何其他可以做到这一点的库也很好(理想情况下它应该理解像 @JsonProperty 这样的 Jackson 注释)。

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

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 的更多信息

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

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

By implementing it, we can define how the name of a property will be resolved during validation. In our case, we want to read the value from the Jackson configuration.

正在创建一个验证器:

 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 中找到:

  1. 建立在@Lukasz 的 上,它会给你 属性 蛇形或任何你想要的格式的名字,但 当您提供 @JsonProperty().

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

    Path: foobar[0].bar_baz

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

最终输出为

Path: $.foobar[0].bar_baz