Require occasionally null or absent field in array to be marked optional

Closes gh-402
This commit is contained in:
Andy Wilkinson
2017-07-02 10:29:27 +01:00
parent a84bb0c569
commit a38a512033
6 changed files with 321 additions and 24 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/**
* A {@code JsonFieldProcessor} processes a payload's fields, allowing them to be
@@ -32,17 +31,10 @@ import java.util.concurrent.atomic.AtomicReference;
*/
final class JsonFieldProcessor {
boolean hasField(JsonFieldPath fieldPath, Object payload) {
final AtomicReference<Boolean> hasField = new AtomicReference<>(false);
traverse(new ProcessingContext(payload, fieldPath), new MatchCallback() {
@Override
public void foundMatch(Match match) {
hasField.set(true);
}
});
return hasField.get();
boolean hasField(final JsonFieldPath fieldPath, Object payload) {
HasFieldMatchCallback callback = new HasFieldMatchCallback();
traverse(new ProcessingContext(payload, fieldPath), callback);
return callback.fieldFound();
}
Object extract(JsonFieldPath path, Object payload) {
@@ -54,6 +46,11 @@ final class JsonFieldProcessor {
matches.add(match.getValue());
}
@Override
public void absent() {
}
});
if (matches.isEmpty()) {
throw new FieldDoesNotExistException(path);
@@ -74,6 +71,11 @@ final class JsonFieldProcessor {
match.remove();
}
@Override
public void absent() {
}
});
}
@@ -85,6 +87,11 @@ final class JsonFieldProcessor {
match.removeSubsection();
}
@Override
public void absent() {
}
});
}
@@ -128,8 +135,8 @@ final class JsonFieldProcessor {
private void handleMapPayload(ProcessingContext context,
MatchCallback matchCallback) {
Map<?, ?> map = context.getPayload();
Object item = map.get(context.getSegment());
if (item != null || map.containsKey(context.getSegment())) {
if (map.containsKey(context.getSegment())) {
Object item = map.get(context.getSegment());
MapMatch mapMatch = new MapMatch(item, map, context.getSegment(),
context.getParentMatch());
if (context.isLeaf()) {
@@ -142,6 +149,47 @@ final class JsonFieldProcessor {
else if ("*".equals(context.getSegment())) {
handleCollectionPayload(map.values(), matchCallback, context);
}
else {
matchCallback.absent();
}
}
/**
* {@link MatchCallback} use to determine whether a payload has a particular field.
*/
private static final class HasFieldMatchCallback implements MatchCallback {
private MatchType matchType = MatchType.NONE;
@Override
public void foundMatch(Match match) {
this.matchType = this.matchType.combinedWith(
match.getValue() == null ? MatchType.NULL : MatchType.NON_NULL);
}
@Override
public void absent() {
this.matchType = this.matchType.combinedWith(MatchType.ABSENT);
}
boolean fieldFound() {
return this.matchType == MatchType.NON_NULL
|| this.matchType == MatchType.NULL;
}
private static enum MatchType {
ABSENT, MIXED, NONE, NULL, NON_NULL;
MatchType combinedWith(MatchType matchType) {
if (this == NONE || this == matchType) {
return matchType;
}
return MIXED;
}
}
}
private static final class MapMatch implements Match {
@@ -265,6 +313,8 @@ final class JsonFieldProcessor {
void foundMatch(Match match);
void absent();
}
private interface Match {