• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fslev / json-compare / #682

30 Apr 2026 11:29AM UTC coverage: 97.158% (-0.5%) from 97.612%
#682

push

web-flow
Merge f7f38eebc into 4691206c7

262 of 272 new or added lines in 13 files covered. (96.32%)

1 existing line in 1 file now uncovered.

376 of 387 relevant lines covered (97.16%)

0.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.83
/src/main/java/io/json/compare/matcher/UseCase.java
1
package io.json.compare.matcher;
2

3
import com.fasterxml.jackson.databind.JsonNode;
4

5
import java.util.Iterator;
6
import java.util.Optional;
7

8
/**
9
 * Matching use-case encoded in either a field name or a string value of the
10
 * expected JSON. The symbolic prefixes ({@code !}, {@code .*}, {@code !.*}) are
11
 * part of the library's public DSL and are documented in the README.
12
 *
13
 * <p>This enum also centralizes the small parsing helpers that translate a raw
14
 * token into a use-case, strip its marker prefix, and recognize the embedded
15
 * JSON path expression syntax ({@code #(...)}).
16
 */
17
enum UseCase {
1✔
18
    MATCH(""),
1✔
19
    DO_NOT_MATCH("!"),
1✔
20
    MATCH_ANY(".*"),
1✔
21
    DO_NOT_MATCH_ANY("!.*");
1✔
22

23
    static final String JSON_PATH_EXP_PREFIX = "#(";
24
    static final String JSON_PATH_EXP_SUFFIX = ")";
25

26
    private final String value;
27

28
    UseCase(String value) {
1✔
29
        this.value = value;
1✔
30
    }
1✔
31

32
    String getValue() {
NEW
33
        return value;
×
34
    }
35

36
    /**
37
     * Resolves the use-case for a JSON node. Non-textual nodes are always
38
     * {@link #MATCH} since the DSL markers only apply to strings.
39
     */
40
    static UseCase of(JsonNode node) {
41
        return node.isTextual() ? of(node.asText()) : MATCH;
1✔
42
    }
43

44
    /**
45
     * Resolves the use-case for a raw string token.
46
     * Order matters: {@code "!.*"} must be checked before {@code "!"}.
47
     */
48
    static UseCase of(String value) {
49
        if (MATCH_ANY.value.equals(value)) return MATCH_ANY;
1✔
50
        if (DO_NOT_MATCH_ANY.value.equals(value)) return DO_NOT_MATCH_ANY;
1✔
51
        if (value.startsWith(DO_NOT_MATCH.value)) return DO_NOT_MATCH;
1✔
52
        return MATCH;
1✔
53
    }
54

55
    /**
56
     * Strips the DSL marker prefix from a value or field (e.g. {@code "!foo"} →
57
     * {@code "foo"}). Also removes a single leading backslash used to escape an
58
     * otherwise-significant DSL token (e.g. {@code "\!foo"} → {@code "!foo"}).
59
     */
60
    static String sanitize(String value) {
61
        UseCase useCase = of(value);
1✔
62
        if (useCase == DO_NOT_MATCH || useCase == DO_NOT_MATCH_ANY) {
1✔
63
            return value.substring(1);
1✔
64
        }
65
        return removeEscapes(value);
1✔
66
    }
67

68
    /**
69
     * Extracts the JSON path expression wrapped in {@code #(...)} markers,
70
     * returning the inner expression. Empty if the field is not a JSON-path
71
     * expression.
72
     */
73
    static Optional<String> extractJsonPathExp(String field) {
74
        if (field.startsWith(JSON_PATH_EXP_PREFIX) && field.endsWith(JSON_PATH_EXP_SUFFIX)) {
1✔
75
            return Optional.of(field.substring(JSON_PATH_EXP_PREFIX.length(),
1✔
76
                    field.length() - JSON_PATH_EXP_SUFFIX.length()));
1✔
77
        }
78
        return Optional.empty();
1✔
79
    }
80

81
    /**
82
     * Counts how many entries in the given expected node carry a
83
     * "do-not-match" style use-case (either as a field name or as an element),
84
     * including JSON-path markers. The result is used to compute effective
85
     * sizes when {@code *_NON_EXTENSIBLE} modes are active.
86
     */
87
    static int countDoNotMatchEntries(JsonNode expected) {
88
        int count = 0;
1✔
89
        if (expected.isArray()) {
1✔
90
            int size = expected.size();
1✔
91
            for (int i = 0; i < size; i++) {
1✔
92
                JsonNode element = expected.get(i);
1✔
93
                UseCase useCase = of(element);
1✔
94
                if (useCase == DO_NOT_MATCH_ANY || useCase == DO_NOT_MATCH || NodeInspect.isJsonPathNode(element)) {
1✔
95
                    count++;
1✔
96
                }
97
            }
98
        } else if (expected.isObject()) {
1✔
99
            Iterator<String> it = expected.fieldNames();
1✔
100
            while (it.hasNext()) {
1✔
101
                String field = it.next();
1✔
102
                UseCase useCase = of(field);
1✔
103
                if (useCase == DO_NOT_MATCH_ANY || useCase == DO_NOT_MATCH
1✔
104
                        || extractJsonPathExp(field).isPresent()) {
1✔
105
                    count++;
1✔
106
                }
107
            }
1✔
108
        }
109
        return count;
1✔
110
    }
111

112
    private static String removeEscapes(String value) {
113
        if (value == null) {
1✔
NEW
114
            return null;
×
115
        }
116
        if (value.startsWith("\\" + DO_NOT_MATCH.value)
1✔
117
                || value.equals("\\" + DO_NOT_MATCH_ANY.value)
1✔
118
                || value.equals("\\" + MATCH_ANY.value)
1✔
119
                || value.startsWith("\\" + JSON_PATH_EXP_PREFIX)) {
1✔
120
            return value.replaceFirst("\\\\", "");
1✔
121
        }
122
        return value;
1✔
123
    }
124
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc