Из Spring BindingResult в поле JSONPath / JSON Pointer, с Джексоном - PullRequest
4 голосов
/ 04 августа 2020

У меня есть приложение Spring Boot, использующее аннотации javax.validation, и я пытаюсь вернуть дружественные 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 в качестве стандарта, изменение для упрощения задачи не является решением.

Я полагаю, что ObjectMapper Джексона можно было бы каким-то образом использовать для этого преобразования или какой-либо другой части API Джексона, но я ничего не мог найти об этом. Подойдет и любая другая библиотека, которая может это сделать (в идеале она должна понимать аннотации Джексона, такие как @JsonProperty).

Ответы [ 3 ]

4 голосов
/ 12 августа 2020

Вы можете легко сделать это с помощью 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();
  }
}

Подробнее Вы можете найти в документации :

0 голосов
/ 14 августа 2020
  1. Основано на ответе @ Lukasz , который даст вам имя свойства в случае змеи или в любом другом формате, но только , когда вы предоставите @JsonProperty().

    Итак, добавление @JsonProperty("bar_baz") к public int barBaz = 42; AND с использованием JacksonPropertyNodeNameProvider даст вам следующий результат.

    Путь: foobar [0] .bar_baz

Чтобы преобразовать foobar[0].bar_baz в jsonpath, я думаю, что манипуляции со строками должно хватить.
   validator.validate(data).forEach(violation -> {
     System.out.println("Path: " + "$." + violation.getPropertyPath());
   });

И окончательный результат будет

Путь: $ .foobar [ 0] .bar_baz

0 голосов
/ 12 августа 2020

Насколько я понял ваш вопрос, вы ищете путь в свою область. Поскольку нет 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']

Если вы выполните простую замену строки на эту I думаю, это хорошее и простое решение. Я не уверен, можно ли получить что-то подобное с обычным Jackson / Faster XML. JsonPath использует Jackson под капотом.

Подробнее о Jayway JsonPath можно узнать на официальном git репозитории

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...