Skip to content

Commit 27c59b8

Browse files
committed
Add filter, name processor and value processor support to JsonWriter
Update `JsonWriter` to support filtering and processing of names/values. This update will allow us to offer better customization options with structured logging. See gh-42486
1 parent 763266f commit 27c59b8

File tree

4 files changed

+893
-14
lines changed

4 files changed

+893
-14
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/json/JsonValueWriter.java

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,15 @@
2424
import java.util.Map;
2525
import java.util.function.BiConsumer;
2626
import java.util.function.Consumer;
27+
import java.util.function.Predicate;
2728

29+
import org.springframework.boot.json.JsonWriter.MemberPath;
30+
import org.springframework.boot.json.JsonWriter.NameProcessor;
31+
import org.springframework.boot.json.JsonWriter.ValueProcessor;
32+
import org.springframework.boot.util.LambdaSafe;
2833
import org.springframework.util.Assert;
2934
import org.springframework.util.ObjectUtils;
35+
import org.springframework.util.StringUtils;
3036
import org.springframework.util.function.ThrowingConsumer;
3137

3238
/**
@@ -40,6 +46,10 @@ class JsonValueWriter {
4046

4147
private final Appendable out;
4248

49+
private MemberPath path = MemberPath.ROOT;
50+
51+
private final Deque<JsonWriterFiltersAndProcessors> filtersAndProcessors = new ArrayDeque<>();
52+
4353
private final Deque<ActiveSeries> activeSeries = new ArrayDeque<>();
4454

4555
/**
@@ -50,6 +60,14 @@ class JsonValueWriter {
5060
this.out = out;
5161
}
5262

63+
void pushProcessors(JsonWriterFiltersAndProcessors jsonProcessors) {
64+
this.filtersAndProcessors.addLast(jsonProcessors);
65+
}
66+
67+
void popProcessors() {
68+
this.filtersAndProcessors.removeLast();
69+
}
70+
5371
/**
5472
* Write a name value pair, or just a value if {@code name} is {@code null}.
5573
* @param <N> the name type in the pair
@@ -82,6 +100,7 @@ <N, V> void write(N name, V value) {
82100
* @param value the value to write
83101
*/
84102
<V> void write(V value) {
103+
value = processValue(value);
85104
if (value == null) {
86105
append("null");
87106
}
@@ -119,7 +138,7 @@ else if (value instanceof Number || value instanceof Boolean) {
119138
*/
120139
void start(Series series) {
121140
if (series != null) {
122-
this.activeSeries.push(new ActiveSeries());
141+
this.activeSeries.push(new ActiveSeries(series));
123142
append(series.openChar);
124143
}
125144
}
@@ -164,8 +183,10 @@ <E> void writeElements(Consumer<Consumer<E>> elements) {
164183
<E> void writeElement(E element) {
165184
ActiveSeries activeSeries = this.activeSeries.peek();
166185
Assert.notNull(activeSeries, "No series has been started");
167-
activeSeries.appendCommaIfRequired();
186+
this.path = activeSeries.updatePath(this.path);
187+
activeSeries.incrementIndexAndAddCommaIfRequired();
168188
write(element);
189+
this.path = activeSeries.restorePath(this.path);
169190
}
170191

171192
/**
@@ -196,12 +217,17 @@ <N, V> void writePairs(Consumer<BiConsumer<N, V>> pairs) {
196217
}
197218

198219
private <N, V> void writePair(N name, V value) {
199-
ActiveSeries activeSeries = this.activeSeries.peek();
200-
Assert.notNull(activeSeries, "No series has been started");
201-
activeSeries.appendCommaIfRequired();
202-
writeString(name);
203-
append(":");
204-
write(value);
220+
this.path = this.path.child(name.toString());
221+
if (!isFilteredPath()) {
222+
String processedName = processName(name.toString());
223+
ActiveSeries activeSeries = this.activeSeries.peek();
224+
Assert.notNull(activeSeries, "No series has been started");
225+
activeSeries.incrementIndexAndAddCommaIfRequired();
226+
writeString(processedName);
227+
append(":");
228+
write(value);
229+
}
230+
this.path = this.path.parent();
205231
}
206232

207233
private void writeString(Object value) {
@@ -256,6 +282,48 @@ private void append(char ch) {
256282
}
257283
}
258284

285+
private boolean isFilteredPath() {
286+
for (JsonWriterFiltersAndProcessors filtersAndProcessors : this.filtersAndProcessors) {
287+
for (Predicate<MemberPath> pathFilter : filtersAndProcessors.pathFilters()) {
288+
if (pathFilter.test(this.path)) {
289+
return true;
290+
}
291+
}
292+
}
293+
return false;
294+
}
295+
296+
private String processName(String name) {
297+
for (JsonWriterFiltersAndProcessors filtersAndProcessors : this.filtersAndProcessors) {
298+
for (NameProcessor nameProcessor : filtersAndProcessors.nameProcessors()) {
299+
name = processName(name, nameProcessor);
300+
}
301+
}
302+
return name;
303+
}
304+
305+
private String processName(String name, NameProcessor nameProcessor) {
306+
name = nameProcessor.processName(this.path, name);
307+
Assert.state(StringUtils.hasLength(name), "NameProcessor " + nameProcessor + " returned an empty result");
308+
return name;
309+
}
310+
311+
private <V> V processValue(V value) {
312+
for (JsonWriterFiltersAndProcessors filtersAndProcessors : this.filtersAndProcessors) {
313+
for (ValueProcessor<?> valueProcessor : filtersAndProcessors.valueProcessors()) {
314+
value = processValue(value, valueProcessor);
315+
}
316+
}
317+
return value;
318+
}
319+
320+
@SuppressWarnings({ "unchecked", "unchecked" })
321+
private <V> V processValue(V value, ValueProcessor<?> valueProcessor) {
322+
return (V) LambdaSafe.callback(ValueProcessor.class, valueProcessor, this.path, value)
323+
.invokeAnd((call) -> call.processValue(this.path, value))
324+
.get(value);
325+
}
326+
259327
/**
260328
* A series of items that can be written to the JSON output.
261329
*/
@@ -287,16 +355,27 @@ enum Series {
287355
*/
288356
private final class ActiveSeries {
289357

290-
private boolean commaRequired;
358+
private final Series series;
359+
360+
private int index;
361+
362+
private ActiveSeries(Series series) {
363+
this.series = series;
364+
}
365+
366+
MemberPath updatePath(MemberPath path) {
367+
return (this.series != Series.ARRAY) ? path : path.child(this.index);
368+
}
291369

292-
private ActiveSeries() {
370+
MemberPath restorePath(MemberPath path) {
371+
return (this.series != Series.ARRAY) ? path : path.parent();
293372
}
294373

295-
void appendCommaIfRequired() {
296-
if (this.commaRequired) {
374+
void incrementIndexAndAddCommaIfRequired() {
375+
if (this.index > 0) {
297376
append(',');
298377
}
299-
this.commaRequired = true;
378+
this.index++;
300379
}
301380

302381
}

0 commit comments

Comments
 (0)