Есть ли способ проверки правописания ключей объекта JSON по отношению к существующим значениям объекта Java с помощью Jackson (или подобной библиотеки)? - PullRequest
0 голосов
/ 27 декабря 2018

Я десериализирую объект JSON, используя Джексона, и до сих пор этот процесс позволил мне успешно преобразовать объект JSON в объект Java.

Однако я представляю себе сценарий, в котором пользователь отправляет JSON в теле своего запроса, и один или несколько ключей написаны с ошибками.Например, что, если Джексон ожидает {"jurisdiction": "Maine"}, но пользователь неправильно набирает ключ и отправляет {"jrdiction": "Maine"}.

Есть ли способ по существу использовать Джексона, чтобы проверить @JsonProperty значения Java и сравнить его с JSON в запросе, а затем вернуть что-то вроде: Property "jrdiction" doesn't exist. Did you mean "jurisdiction"?

ЯПомните, что Джексон выдаст UnrecognizedPropertyException, когда в Java-классе есть свойства, которых нет.Однако, что если я хочу игнорировать неизвестные свойства (разрешить пользователям отправлять что-либо в объекте JSON), но также иметь проверку орфографии, которая сообщает им, что свойство может быть написано с ошибкой?

Ответы [ 3 ]

0 голосов
/ 28 декабря 2018

Решение состоит из нескольких частей:

  • Во-первых, вам нужно знать все ключи json, которые не совпадают с полем в объекте json.
  • Во-вторых, вам нужночтобы узнать доступные поля json для объектов, чтобы найти наиболее близкое совпадение с json-ключом с орфографической ошибкой.
  • Наконец, вам нужен способ вычисления ближайших совпадений между несоответствующим ключом json и доступными полями json.

Вы можете получить все ключи данных JSON, которые не соответствуют ни одному из полей объекта JSON, используя @JsonAnySetter, как предложено Шиванг Агарвал .

public static class MyParent {
    @JsonProperty("a") protected String jsonA;
    @JsonProperty("b") protected String jsonB;
    // ignored by getJsonPropertyNames()
    protected String internal1;
    @JsonIgnore private Map<String, Object> additionalProperties = new HashMap<String, Object>();
    @JsonAnyGetter public Map<String, Object> getAdditionalProperties() {
        return this.additionalProperties;
    }
    @JsonAnySetter public void setAdditionalProperty(String name, Object value) {
        this.additionalProperties.put(name, value);
    }
}
public static class MyChild extends MyParent {
    @JsonProperty("jurisdiction") protected String jurisdiction;
    // ignored by getJsonPropertyNames()
    protected String internal2;
}

Вы можете получить все доступные поля JSON (отмеченные @JsonProperty) из объекта, используя следующие методы:

private static Collection<String> getJsonPropertyNames(Object o) {
    // might need checking if fields collide
    // Eg:
    //   @JSONProperty String field1;
    //   @JSONProperty("field1") String fieldOne;
    // maybe should be a Set?
    List<String> fields = new ArrayList<>();
    forAllFields(o, (f) -> {
        JsonProperty jprop = f.getAnnotation(JsonProperty.class);
        if (jprop != null) {
            String fieldName = jprop.value();
            if (fieldName == null) {
                fieldName = f.getName();
            }
            fields.add(fieldName);
        }
    });
    return fields;
}

/** For all fields of the given object, including its parent fields */
private static void forAllFields(Object o, Consumer<Field> consumer) {
    Class<?> klass = o.getClass();
    while (klass != null) {
        for (Field f : klass.getDeclaredFields())
            consumer.accept(f);
        klass = klass.getSuperclass();
    }
}

public static void main(String[] args) throws IOException {
    for (String s : getJsonPropertyNames(new MyChild()))
        System.out.println(s);
}

Наиболее похожие строки можно найти с помощью приведенного нижеметоды:

Я все еще хочу проверить мой метод stringEditDistance еще немного, но пока он может работать достаточно хорошо.Я мог бы поработать над этим позже.

/** finds the nearest matching string from the options
  * using the basic string edit distance where all operations cost 1 */
private static String findNearestMatch(String input, Iterable<String> options) {
    String closestString = null;
    int minDistance = Integer.MAX_VALUE;
    for (String option : options) { 
        int distance = stringEditDistance(input, option, 1, 1, (a, b) -> 1);
        if (distance < minDistance) {
            minDistance = distance;
            closestString = option;
        }
    }
    return closestString;
}

/**
 * NOTE: needs some editing and more testing.
 *
 * Returns the minimum cost to edit the input string into the target string using the given costs for
 * operations.
 * 
 * @param insertCost
 *            the cost to insert a character into the input to bring it closer to the target
 * @param deleteCost
 *            the cost to delete a character from the input to bring it closer to the target
 * @param replaceCostCalculator
 *            a function to calculate the cost to replace a character in the input to bring it close
 *            to the target
 */
public static int stringEditDistance(String input, String target, int insertCost, int deleteCost,
        BiFunction<Character, Character, Integer> replaceCalculator) {
    int[][] dp = new int[input.length() + 1][target.length() + 1];
    for (int i = 0; i <= input.length(); i++)
        dp[i][0] = i;
    for (int j = 0; j <= target.length(); j++)
        dp[0][j] = j;

    for (int i = 0; i < input.length(); i++) {
        char cInput = input.charAt(i);
        for (int j = 0; j < target.length(); j++) {
            char cTarget = target.charAt(j);
            if (cInput == cTarget) {
                dp[i + 1][j + 1] = dp[i][j];
            } else {
                int replace = dp[i][j] + replaceCalculator.apply(cInput, cTarget);
                int insert = dp[i][j + 1] + insertCost;
                int delete = dp[i + 1][j] + deleteCost;
                int min = Math.min(replace, Math.min(insert, delete));
                dp[i + 1][j + 1] = min;
            }
        }
    }
    return dp[input.length()][target.length()];
}

public static void main(String[] args) throws IOException {
    // serialize a json object
    // edit this json to test with other bad input keys
    final String json = "{ \"a\" : \"1\", \"b\" : \"2\", \"jrdiction\" : \"3\" }";
    MyChild child = new ObjectMapper().readerFor(MyChild.class).readValue(json);

    // List<String> jsonProps = getJsonPropertyNames(child);
    // create the list of jsonProps for yourself so you can edit and test easily
    List<String> jsonProps = Arrays.asList("a", "b", "jurisdiction");
    for (Entry<String, Object> e : child.getAdditionalProperties().entrySet()) {
        String nearest = findNearestMatch(e.getKey(), jsonProps);
        System.out.println(e.getKey() + " is closest to " + nearest);
    }
}
0 голосов
/ 28 декабря 2018

Ваш вопрос очень широкий, но я постараюсь дать вам некоторые отправные точки для дальнейшего исследования на простом примере.

Допустим, у вас есть такой класс:

@Getter @Setter
@AllArgsConstructor
public class MyClass {
    private String name;
    private Integer age;
}

Затем вы пытаетесь десериализовать JSON следующим образом:

{
    "name": "Nomen est Omen",
    "agge": 1
}

Как мы знаем, он не работает из-за ошибки age.Чтобы получить больше контроля над тем, что происходит при десериализации, вы можете реализовать свой собственный десериализатор, например:

@SuppressWarnings("serial")
public class MyClassDeserializer extends StdDeserializer<MyClass> {

    public MyClassDeserializer() {
        super((Class<?>) null);
    }

    @Override
    public MyClass deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        // Your logic goes here
        // I suppose here now - for brevity - that there will not be a problem with name
        String name = node.get("name").asText();
        // And that there might be with age which should not be null
        int age;
        String correct = "age";
        try {
            age = node.get("age").asInt();
            return new MyClass(name, age);
        } catch (Exception e) {
            String wrong = magicalStringProximityMethod(correct, node);
            throw new IllegalArgumentException("Property '" + wrong + "' doesn't exist. Did you mean '" + correct + "'?");
        }
    }

    // This returns the closest match in nodes props dor the correct string.
    private String magicalStringProximityMethod(String correct, JsonNode node) {
        Iterator<Entry<String, JsonNode>> iter = node.fields();
        // iterate fields find the closest match
        // Somehow it happems to 'agge' this time
        return "agge";
    }
}

Может быть несколько способов реализовать это в зависимости от реальной потребности.Эта реализация подходит к проблеме так, что она заботится о возможных полях с ошибками в JSON, только если поле POJO не может быть заполнено.

0 голосов
/ 27 декабря 2018

Насколько я знаю, я не думаю, что у Джексона есть такая поддержка, но один из способов добиться этого может быть достигнут путем добавления приведенного ниже кода в ваш класс POJO.

@JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();

@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}

@JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}

Этот установщик и получательдобавит на карту все несопоставимые или неизвестные ключи / свойства, которые недоступны в вашем POJO.

Затем вы можете проверить размер карты и, если она не равна нулю, вы можете найти наиболее релевантные.ключи для этого неизвестного ключа (ей).Могут быть шансы, если ключ может иметь больше совпадений.Теперь все зависит от вас, если вы захотите.

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