Skip to content

Allow custom masking config without defining allowed keys#319

Open
andrebrait wants to merge 1 commit into
Breus:masterfrom
andrebrait:master
Open

Allow custom masking config without defining allowed keys#319
andrebrait wants to merge 1 commit into
Breus:masterfrom
andrebrait:master

Conversation

@andrebrait

Copy link
Copy Markdown

Fixes #317

@github-actions

Copy link
Copy Markdown

Note

These results are affected by shared workloads on GitHub runners. Use the results only to detect possible regressions, but always rerun on a more stable machine before drawing conclusions!
Regressions/improvements are highlighted when the difference exceeds 3.0%.

Benchmark results

BaselineBenchmark

Method characters jsonSize maskedKeyProbability master (ops/s) PR (ops/s) change master alloc (B/op) PR alloc (B/op) alloc change
jacksonParseOnly unicode 1kb 0.1 88,198 84,898 🔴 -3.7% 17,616.0 17,704.0 ⚪ +0.5%
regexReplace unicode 1kb 0.1 6,156 4,156 🔴 -32.5% 53,048.4 53,048.6 ⚪ +0.0%
writeFile unicode 1kb 0.1 6,616 5,463 🔴 -17.4% 5,920.4 5,920.4 ⚪ +0.0%
Full results — BaselineBenchmark
Method characters jsonSize maskedKeyProbability master (ops/s) PR (ops/s) change master alloc (B/op) PR alloc (B/op) alloc change
countBytes unicode 1kb 0.1 2,735,298 2,770,020 ⚪ +1.3% 0.0010 0.0010 ⚪ +0.0%
jacksonParseAndMask unicode 1kb 0.1 38,678 39,779 ⚪ +2.8% 58,456.1 58,816.1 ⚪ +0.6%
jacksonParseOnly unicode 1kb 0.1 88,198 84,898 🔴 -3.7% 17,616.0 17,704.0 ⚪ +0.5%
regexReplace unicode 1kb 0.1 6,156 4,156 🔴 -32.5% 53,048.4 53,048.6 ⚪ +0.0%
writeFile unicode 1kb 0.1 6,616 5,463 🔴 -17.4% 5,920.4 5,920.4 ⚪ +0.0%

InstanceCreationBenchmark

No significant changes (all within 3.0%).

Full results — InstanceCreationBenchmark
Method numberOfTargetKeys master (ops/s) PR (ops/s) change master alloc (B/op) PR alloc (B/op) alloc change
jsonMasker 1000 1,595 1,575 ⚪ -1.3% 1,637,212 1,637,532 ⚪ +0.0%

JsonMaskerBenchmark

Method characters jsonPath jsonSize maskedKeyProbability master (ops/s) PR (ops/s) change master alloc (B/op) PR alloc (B/op) alloc change
jsonMaskerByteArrayStreams unicode false 1kb 0.1 237,686 245,904 🟢 +3.5% 10,840.0 10,840.0 ⚪ -0.0%
jsonMaskerByteArrayStreams unicode true 1kb 0.1 231,219 243,330 🟢 +5.2% 12,184.0 12,184.0 ⚪ +0.0%
jsonMaskerBytes unicode false 1kb 0.1 433,825 419,785 🔴 -3.2% 2,272.0 2,272.0 ⚪ +0.0%
jsonMaskerBytes unicode true 1kb 0.1 444,187 404,853 🔴 -8.9% 2,024.0 2,024.0 ⚪ +0.0%
jsonMaskerString unicode true 1kb 0.1 216,306 204,972 🔴 -5.2% 10,944.0 10,944.0 ⚪ +0.0%
Full results — JsonMaskerBenchmark
Method characters jsonPath jsonSize maskedKeyProbability master (ops/s) PR (ops/s) change master alloc (B/op) PR alloc (B/op) alloc change
jsonMaskerByteArrayStreams unicode false 1kb 0.1 237,686 245,904 🟢 +3.5% 10,840.0 10,840.0 ⚪ -0.0%
jsonMaskerByteArrayStreams unicode true 1kb 0.1 231,219 243,330 🟢 +5.2% 12,184.0 12,184.0 ⚪ +0.0%
jsonMaskerBytes unicode false 1kb 0.1 433,825 419,785 🔴 -3.2% 2,272.0 2,272.0 ⚪ +0.0%
jsonMaskerBytes unicode true 1kb 0.1 444,187 404,853 🔴 -8.9% 2,024.0 2,024.0 ⚪ +0.0%
jsonMaskerString unicode false 1kb 0.1 233,641 228,372 ⚪ -2.3% 10,176.0 10,176.0 ⚪ +0.0%
jsonMaskerString unicode true 1kb 0.1 216,306 204,972 🔴 -5.2% 10,944.0 10,944.0 ⚪ +0.0%

LargeKeySetInstanceCreationBenchmark

Method keyLength numberOfTargetKeys master (ops/s) PR (ops/s) change master alloc (B/op) PR alloc (B/op) alloc change
jsonMasker 100 1000 130.93 135.42 🟢 +3.4% 32,372,229 32,372,547 ⚪ +0.0%
Full results — LargeKeySetInstanceCreationBenchmark
Method keyLength numberOfTargetKeys master (ops/s) PR (ops/s) change master alloc (B/op) PR alloc (B/op) alloc change
jsonMasker 100 1000 130.93 135.42 🟢 +3.4% 32,372,229 32,372,547 ⚪ +0.0%

StreamTypeBenchmark

Method jsonSize streamInputType streamOutputType master (ops/s) PR (ops/s) change master alloc (B/op) PR alloc (B/op) alloc change
jsonMaskerStreams 1kb ByteArrayStream ByteArrayStream 246,470 237,293 🔴 -3.7% 12,184.0 12,184.0 ⚪ +0.0%
Full results — StreamTypeBenchmark
Method jsonSize streamInputType streamOutputType master (ops/s) PR (ops/s) change master alloc (B/op) PR alloc (B/op) alloc change
jsonMaskerStreams 1kb ByteArrayStream ByteArrayStream 246,470 237,293 🔴 -3.7% 12,184.0 12,184.0 ⚪ +0.0%
jsonMaskerStreams 1kb ByteArrayStream FileStream 5,267 5,335 ⚪ +1.3% 9,208.5 9,208.5 ⚪ -0.0%
jsonMaskerStreams 1kb FileStream ByteArrayStream 92,286 92,599 ⚪ +0.3% 12,336.0 12,336.0 ⚪ -0.0%
jsonMaskerStreams 1kb FileStream FileStream 5,120 5,240 ⚪ +2.3% 9,360.5 9,360.5 ⚪ -0.0%

ValueMaskerBenchmark

Method characters jsonSize maskedKeyProbability master (ops/s) PR (ops/s) change master alloc (B/op) PR alloc (B/op) alloc change
maskWithStatic unicode 1kb 0.1 650,067 670,852 🟢 +3.2% 1,272.0 1,272.0 ⚪ -0.0%
maskWithTextValueFunction unicode 1kb 0.1 574,364 538,182 🔴 -6.3% 1,776.0 1,776.0 ⚪ +0.0%
Full results — ValueMaskerBenchmark
Method characters jsonSize maskedKeyProbability master (ops/s) PR (ops/s) change master alloc (B/op) PR alloc (B/op) alloc change
maskWithRawValueFunction unicode 1kb 0.1 575,326 581,830 ⚪ +1.1% 1,632.0 1,632.0 ⚪ +0.0%
maskWithStatic unicode 1kb 0.1 650,067 670,852 🟢 +3.2% 1,272.0 1,272.0 ⚪ -0.0%
maskWithTextValueFunction unicode 1kb 0.1 574,364 538,182 🔴 -6.3% 1,776.0 1,776.0 ⚪ +0.0%
Raw output (PR @ 4514245)
Benchmark                                                           (characters)  (jsonPath)  (jsonSize)  (keyLength)  (maskedKeyProbability)  (numberOfTargetKeys)  (streamInputType)  (streamOutputType)   Mode  Cnt         Score       Error   Units
BaselineBenchmark.countBytes                                             unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4   2770019.979 ± 41990.260   ops/s
BaselineBenchmark.jacksonParseAndMask                                    unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4     39779.454 ±  1031.173   ops/s
BaselineBenchmark.jacksonParseOnly                                       unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4     84898.340 ±   273.245   ops/s
BaselineBenchmark.regexReplace                                           unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4      4155.957 ±   130.394   ops/s
BaselineBenchmark.writeFile                                              unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4      5463.109 ±   297.075   ops/s
InstanceCreationBenchmark.jsonMasker                                         N/A         N/A         N/A          N/A                     N/A                  1000                N/A                 N/A  thrpt    4      1575.202 ±    26.642   ops/s
JsonMaskerBenchmark.jsonMaskerByteArrayStreams                           unicode       false         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    245904.109 ±  2448.321   ops/s
JsonMaskerBenchmark.jsonMaskerByteArrayStreams                           unicode        true         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    243330.132 ±  7775.156   ops/s
JsonMaskerBenchmark.jsonMaskerBytes                                      unicode       false         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    419785.047 ± 21225.130   ops/s
JsonMaskerBenchmark.jsonMaskerBytes                                      unicode        true         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    404852.626 ±  6044.757   ops/s
JsonMaskerBenchmark.jsonMaskerString                                     unicode       false         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    228371.942 ±  7310.931   ops/s
JsonMaskerBenchmark.jsonMaskerString                                     unicode        true         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    204972.180 ±  8705.647   ops/s
LargeKeySetInstanceCreationBenchmark.jsonMasker                              N/A         N/A         N/A          100                     N/A                  1000                N/A                 N/A  thrpt    4       135.416 ±     1.083   ops/s
StreamTypeBenchmark.jsonMaskerStreams                                        N/A         N/A         1kb          N/A                     N/A                   N/A    ByteArrayStream     ByteArrayStream  thrpt    4    237293.222 ±  4065.682   ops/s
StreamTypeBenchmark.jsonMaskerStreams                                        N/A         N/A         1kb          N/A                     N/A                   N/A    ByteArrayStream          FileStream  thrpt    4      5334.898 ±  1082.588   ops/s
StreamTypeBenchmark.jsonMaskerStreams                                        N/A         N/A         1kb          N/A                     N/A                   N/A         FileStream     ByteArrayStream  thrpt    4     92599.466 ±  3611.932   ops/s
StreamTypeBenchmark.jsonMaskerStreams                                        N/A         N/A         1kb          N/A                     N/A                   N/A         FileStream          FileStream  thrpt    4      5239.612 ±   649.739   ops/s
ValueMaskerBenchmark.maskWithRawValueFunction                            unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    581830.456 ± 12103.849   ops/s
ValueMaskerBenchmark.maskWithStatic                                      unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    670852.158 ± 12663.060   ops/s
ValueMaskerBenchmark.maskWithTextValueFunction                           unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    538181.848 ± 22586.720   ops/s
Raw output (master @ 550a7d4)
Benchmark                                                           (characters)  (jsonPath)  (jsonSize)  (keyLength)  (maskedKeyProbability)  (numberOfTargetKeys)  (streamInputType)  (streamOutputType)   Mode  Cnt         Score       Error   Units
BaselineBenchmark.countBytes                                             unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4   2735297.765 ± 24291.234   ops/s
BaselineBenchmark.jacksonParseAndMask                                    unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4     38678.072 ±   685.563   ops/s
BaselineBenchmark.jacksonParseOnly                                       unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4     88197.738 ±  1340.350   ops/s
BaselineBenchmark.regexReplace                                           unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4      6156.064 ±   149.833   ops/s
BaselineBenchmark.writeFile                                              unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4      6615.522 ±  2255.610   ops/s
InstanceCreationBenchmark.jsonMasker                                         N/A         N/A         N/A          N/A                     N/A                  1000                N/A                 N/A  thrpt    4      1595.202 ±    39.959   ops/s
JsonMaskerBenchmark.jsonMaskerByteArrayStreams                           unicode       false         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    237686.357 ±  1073.330   ops/s
JsonMaskerBenchmark.jsonMaskerByteArrayStreams                           unicode        true         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    231218.946 ±  1928.180   ops/s
JsonMaskerBenchmark.jsonMaskerBytes                                      unicode       false         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    433825.028 ±  6113.063   ops/s
JsonMaskerBenchmark.jsonMaskerBytes                                      unicode        true         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    444187.119 ± 13016.017   ops/s
JsonMaskerBenchmark.jsonMaskerString                                     unicode       false         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    233640.578 ±  2344.916   ops/s
JsonMaskerBenchmark.jsonMaskerString                                     unicode        true         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    216306.405 ±  1768.176   ops/s
LargeKeySetInstanceCreationBenchmark.jsonMasker                              N/A         N/A         N/A          100                     N/A                  1000                N/A                 N/A  thrpt    4       130.925 ±     6.872   ops/s
StreamTypeBenchmark.jsonMaskerStreams                                        N/A         N/A         1kb          N/A                     N/A                   N/A    ByteArrayStream     ByteArrayStream  thrpt    4    246470.021 ±  3409.883   ops/s
StreamTypeBenchmark.jsonMaskerStreams                                        N/A         N/A         1kb          N/A                     N/A                   N/A    ByteArrayStream          FileStream  thrpt    4      5267.016 ±   170.918   ops/s
StreamTypeBenchmark.jsonMaskerStreams                                        N/A         N/A         1kb          N/A                     N/A                   N/A         FileStream     ByteArrayStream  thrpt    4     92285.628 ±  1055.138   ops/s
StreamTypeBenchmark.jsonMaskerStreams                                        N/A         N/A         1kb          N/A                     N/A                   N/A         FileStream          FileStream  thrpt    4      5120.451 ±   459.907   ops/s
ValueMaskerBenchmark.maskWithRawValueFunction                            unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    575325.768 ±   938.470   ops/s
ValueMaskerBenchmark.maskWithStatic                                      unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    650067.390 ±  9936.826   ops/s
ValueMaskerBenchmark.maskWithTextValueFunction                           unicode         N/A         1kb          N/A                     0.1                   N/A                N/A                 N/A  thrpt    4    574364.461 ± 10574.737   ops/s

@Breus Breus requested review from donavdey and gavlyukovskiy April 22, 2026 18:46
@andrebrait

Copy link
Copy Markdown
Author

I'll take a look and fix it. Weirdly enough, it all passed locally.

private static final JsonPathParser JSON_PATH_PARSER = new JsonPathParser();

private final Set<String> targetKeys = new HashSet<>();
private final Set<String> allowedKeys = new HashSet<>();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrebrait I don't think this can be called allowedKeys it is only allowed in allow mode, they are target keys and have a TargetKeyMode

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably an accidental rename. I'll fix it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses issue #317 where JSONPath-specific masking configs were ignored in ALLOW mode when no allowed JSONPaths were configured, enabling “mask everything by default, but apply specific configs to selected keys/paths”.

Changes:

  • Add coverage for ALLOW mode with empty allowJsonPaths() while still applying key/JSONPath-specific configs.
  • Store JSONPath-specific masking configs separately from key configs and wire them into matcher initialization.
  • Minor refactors in JSONPath parsing and ambiguity checking (including making JsonPath comparable).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/test/java/dev/blaauwendraad/masker/json/CustomKeyMaskingConfigTest.java Adds a regression test for ALLOW mode with empty allowed JSONPaths while using specific configs.
src/main/java/dev/blaauwendraad/masker/json/path/JsonPathParser.java Refactors segment parsing and simplifies ambiguity sorting via Comparable.
src/main/java/dev/blaauwendraad/masker/json/path/JsonPath.java Implements Comparable<JsonPath> to support natural sorting.
src/main/java/dev/blaauwendraad/masker/json/config/JsonMaskingConfig.java Introduces separate JSONPath config storage and exposes JSONPath config accessors.
src/main/java/dev/blaauwendraad/masker/json/KeyMatcher.java Inserts JSONPath config keys into the trie and resolves configs from either key or JSONPath config maps.
src/main/java/dev/blaauwendraad/masker/json/KeyContainsMasker.java Ensures JSONPath tracking is enabled when JSONPath configs exist even if allow-list JSONPaths are empty.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 329 to 333
targetJsonPaths.add(parsed);
}
if (config != null) {
targetKeyConfigs.put(parsed.toString(), config);
targetJsonPathConfigs.put(parsed, config);
}

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addMaskJsonPath now stores configs in targetJsonPathConfigs (instead of targetKeyConfigs), but the duplicate detection logic above still checks targetKeyConfigs.containsKey(parsed.toString()). In ALLOW mode (where the path isn’t added to targetJsonPaths), adding the same JSONPath config twice will no longer be rejected and will silently overwrite. Please update the duplicate check to consult targetJsonPathConfigs for this case.

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +92
if (symbol == '.' || (symbol == '[' && !segment.isEmpty())) {
segments.add(segment.toString());
segment = new StringBuilder();
segment.setLength(0);

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseSegments uses segment.isEmpty() on a StringBuilder. This method is not available on JDK 11 (the library’s minimum runtime), so this can compile against a newer JDK but fail at runtime with NoSuchMethodError on Java 11. Use segment.length() != 0 (or segment.length() > 0) instead.

Copilot uses AI. Check for mistakes.
Comment on lines +105 to 106
if (!segment.isEmpty() || literal.endsWith("[]")) {
segments.add(segment.toString());

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same JDK 11 compatibility issue here: StringBuilder#isEmpty() is not available on Java 11. Please replace with a segment.length() check.

Copilot uses AI. Check for mistakes.
* Returns the config for the given JSON path. If no specific config is available for the given path, returns {@code null}.
*
* @param path the JSON path to be masked
* @return the config for the given key

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc mismatch: getJsonPathConfig documents @return the config for the given key, but the method is for a JSON path. Please update the return description to refer to the JSON path to avoid confusion in generated docs.

Suggested change
* @return the config for the given key
* @return the config for the given JSON path

Copilot uses AI. Check for mistakes.
Comment on lines +427 to +432
public Builder maskWith(KeyMaskingConfig maskingConfig) {
defaultConfigBuilder.maskStringsWith(maskingConfig.getStringValueMasker())
.maskBooleansWith(maskingConfig.getBooleanValueMasker())
.maskNumbersWith(maskingConfig.getNumberValueMasker());
return this;
}

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maskWith should validate maskingConfig similarly to the other builder methods (e.g., Objects.requireNonNull(maskingConfig)), so callers get a clear exception at the API boundary instead of an indirect NPE from get*ValueMasker().

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSONPath-specific masking config gets ignored if the list of allowed JSONPaths is empty

3 participants