英文:
From Spring BindingResult to field JSONPath/JSON Pointer, with Jackson
问题
I have a Spring Boot application using javax.validation
annotations and I'm trying to return friendly JSON error messages pointing to the offending field, yet converting from the available "Java-object" path to either JSONPath or JSON Pointer is something I'm not finding a way to do.
SSCO sample:
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;
}
}
Output:
Serialized: {
"foobar" : [ {
"bar_baz" : 42
} ]
}
Path: foo[0].barBaz
As you can see, I need to convert foo[0].barBaz
into either $.foobar[0].bar_baz
or /foobar/0/bar_baz
. The parsed object (the data
variable above) is also provided by the BindingResult
object that holds the validation information.
I thought about doing some String manipulation, but that's messy, hacky, and can break easily with @JsonProperty
which I would need to handle separately, maybe other corner cases that I didn't think about. Plus, we use SNAKE_CASE
as a standard, changing to simplify the task is not a solution.
I suppose Jackson's ObjectMapper
could be used somehow to make this conversion, or some other piece of Jackson API, but I couldn't find anything about that. Any other library that can do this is also fine (ideally it should understand Jackson annotations like @JsonProperty
).
英文:
I have a Spring Boot application using javax.validation
annotations and I'm trying to return friendly JSON error messages pointing to the offending field, yet converting from the available "Java-object" path to either JSONPath or JSON Pointer is something I'm not finding a way to do.
SSCO sample:
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;
}
}
Output:
Serialized: {
"foobar" : [ {
"bar_baz" : 42
} ]
}
Path: foo[0].barBaz
As you can see, I need to convert foo[0].barBaz
into either $.foobar[0].bar_baz
or /foobar/0/bar_baz
. The parsed object (the data
variable above) is also provided by the BindingResult
object that holds the validation information.
I thought about doing some String manipulation, but that's messy, hacky, and can break easily with @JsonProperty
which I would need to handle separately, maybe other corner cases that I didn't think about. Plus, we use SNAKE_CASE
as a standard, changing to simplify the task is not a solution.
I suppose Jackson's ObjectMapper
could be used somehow to make this conversion, or some other piece of Jackson API, but I couldn't find anything about that. Any other library that can do this is also fine (ideally it should understand Jackson annotations like @JsonProperty
).
答案1
得分: 6
您可以轻松使用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();
}
}
更多详情请参阅文档:
英文:
You can do it easily with Hibernate Validator 6.1.5.
You need to provide your own implementation of 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.
Creating a validator:
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();
}
}
More details You can find in documentation:
答案2
得分: 0
以下是翻译好的部分:
"As far as I understood your question You are seeking path to your field. As there is no I/P JSON I have taken one example for you."
"根据我理解你的问题,你正在寻找字段的路径。由于没有输入的 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);
}
}
}
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);
}
}
}
"Will output exactly"
"将精确输出如下:"
$['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。"
"如果你对这个进行一些简单的字符串替换,我认为这是一个不错且简单的解决方案。我不确定是否可以使用纯粹的 Jackson/FasterXML 获得类似的结果。JsonPath 在底层使用了 Jackson。"
"You can get to more about Jayway JsonPath on official git repo"
"你可以在官方 git 仓库上了解更多关于 Jayway JsonPath 的信息。"
英文:
As far as I understood your question You are seeking path to your field. As there is no I/P JSON I have taken one example for you.
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);
}
}
}
Will output exactly
$['top_field']['mid_field'][0]['my_field']
$['top_field']['mid_field'][1]['my_field']
$['top_field']['another_mid_field'][0]['my_field']
If you do some simple string replace on that one I think it´s a nice and easy solution. I`m not sure if you can get anything similar with plain Jackson/FasterXML. JsonPath uses Jackson under the hood.
You can get to more about Jayway JsonPath on official git repo
答案3
得分: 0
-
基于 @Lukasz 的回答,链接,这将在你提供
@JsonProperty()
时提供属性名称的蛇形命名或其他格式,例如:因此,将
@JsonProperty("bar_baz")
添加到public int barBaz = 42;
并且 使用JacksonPropertyNodeNameProvider
将会得到以下输出:路径:foobar[0].bar_baz
- 要将
foobar[0].bar_baz
转换为 JSON 路径,我认为字符串操作应该足够了。
validator.validate(data).forEach(violation -> {
System.out.println("路径:" + "$." + violation.getPropertyPath());
});
最终输出将是
路径:$.foobar[0].bar_baz
英文:
-
Building on @Lukasz's answer, which will give you property name in snake case or whatever format you want but only when you provide
@JsonProperty()
.So, adding
@JsonProperty("bar_baz")
topublic int barBaz = 42;
AND usingJacksonPropertyNodeNameProvider
will give you following output.
> Path: foobar[0].bar_baz
- To convert
foobar[0].bar_baz
to jsonpath, I think String manipulation should suffice.
validator.validate(data).forEach(violation -> {
System.out.println("Path: " + "$." + violation.getPropertyPath());
});
And final output would be
> Path: $.foobar[0].bar_baz
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论