我正在使用Jackson来反序列化JSON对象,到目前为止,该过程已使我成功地将JSON对象转换为Java对象。
但是,我正在想象一个场景,在该场景中,用户在请求的正文中发送JSON,并且一个或多个键的拼写错误。例如,如果Jackson期望{"jurisdiction": "Maine"}
但用户拼写错误的密钥并发送{"jrdiction": "Maine"}
,该怎么办。
有没有一种方法可以使用Jackson来检查Java值的@JsonProperty并将其与请求中的JSON进行比较,然后返回诸如:Property "jrdiction" doesn't exist. Did you mean "jurisdiction"?
我知道,当Java类中不存在某些属性时,杰克逊会抛出UnrecognizedPropertyException
。但是,如果我想忽略未知属性(允许用户在JSON对象中发送任何内容),但又进行拼写检查以告知他们某个属性可能拼写错误该怎么办?
答案 0 :(得分:1)
据我所知,我不认为杰克逊有这种支持,但是可以通过在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);
}
此setter和getter将在POJO中不可用的所有不匹配或未知的键/属性添加到地图中。
然后,您可以检查地图的大小,以及地图的大小是否非零,然后您可以查找该未知钥匙最相关的钥匙。如果密钥可以包含多个匹配项,则可能会出现机会。现在,由您决定如何处理它。
答案 1 :(得分:0)
解决方案包括多个部分:
您可以按照Shivang Agarwal的建议,使用@JsonAnySetter
获取与任何JSON对象字段都不匹配的所有JSON数据密钥。
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);
}
}
答案 2 :(得分:0)
您的问题非常广泛,但我将尝试通过一个简单的例子为您提供一些进一步研究的起点。
让我们假设您有一个像这样的课程:
@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";
}
}
根据实际需要,可能有几种方法可以实现此目的。此实现解决了该问题,因此,仅当无法填充POJO字段时,它才会关心JSON中可能拼写错误的字段。