Member Class — spring-boot Architecture
Architecture documentation for the Member class in JsonWriter.java from the spring-boot codebase.
Entity Profile
Relationship Graph
Source Code
core/spring-boot/src/main/java/org/springframework/boot/json/JsonWriter.java lines 369–768
final class Member<T> {
private final int index;
private final @Nullable String name;
private ValueExtractor<? extends @Nullable T> valueExtractor;
private @Nullable BiConsumer<T, BiConsumer<?, ?>> pairs;
private @Nullable Members<T> members;
Member(int index, @Nullable String name, ValueExtractor<? extends @Nullable T> valueExtractor) {
this.index = index;
this.name = name;
this.valueExtractor = valueExtractor;
}
/**
* Only include this member when its value is not {@code null}.
* @return a {@link Member} which may be configured further
*/
public Member<T> whenNotNull() {
return when(Objects::nonNull);
}
/**
* Only include this member when an extracted value is not {@code null}.
* @param extractor an function used to extract the value to test
* @return a {@link Member} which may be configured further
*/
public Member<T> whenNotNull(Function<@Nullable T, ?> extractor) {
Assert.notNull(extractor, "'extractor' must not be null");
return when((instance) -> Objects.nonNull(extractor.apply(instance)));
}
/**
* Only include this member when it is not {@code null} and has a
* {@link Object#toString() toString()} that is not zero length.
* @return a {@link Member} which may be configured further
* @see StringUtils#hasLength(CharSequence)
*/
public Member<T> whenHasLength() {
return when((instance) -> instance != null && StringUtils.hasLength(instance.toString()));
}
/**
* Only include this member when it is not empty (See
* {@link ObjectUtils#isEmpty(Object)} for details).
* @return a {@link Member} which may be configured further
*/
public Member<T> whenNotEmpty() {
Predicate<@Nullable T> isEmpty = ObjectUtils::isEmpty;
return whenNot(isEmpty);
}
/**
* Only include this member when the given predicate does not match.
* @param predicate the predicate to test
* @return a {@link Member} which may be configured further
*/
public Member<T> whenNot(Predicate<@Nullable T> predicate) {
Assert.notNull(predicate, "'predicate' must not be null");
return when(predicate.negate());
}
/**
* Only include this member when the given predicate matches.
* @param predicate the predicate to test
* @return a {@link Member} which may be configured further
*/
public Member<T> when(Predicate<? super @Nullable T> predicate) {
Assert.notNull(predicate, "'predicate' must not be null");
this.valueExtractor = this.valueExtractor.when(predicate);
return this;
}
/**
* Adapt the value by applying the given {@link Function}.
* @param <R> the result type
* @param extractor a {@link Extractor} to adapt the value
* @return a {@link Member} which may be configured further
*/
@SuppressWarnings("unchecked")
public <R> Member<R> as(Extractor<T, R> extractor) {
Assert.notNull(extractor, "'adapter' must not be null");
Member<R> result = (Member<R>) this;
result.valueExtractor = this.valueExtractor.as(extractor::extract);
return result;
}
/**
* Add JSON name/value pairs by extracting values from a series of elements.
* Typically used with a {@link Iterable#forEach(Consumer)} call, for example:
*
* <pre class="code">
* members.add(Event::getTags).usingExtractedPairs(Iterable::forEach, pairExtractor);
* </pre>
* <p>
* When used with a named member, the pairs will be added as a new JSON value
* object:
*
* <pre>
* {
* "name": {
* "p1": 1,
* "p2": 2
* }
* }
* </pre>
*
* When used with an unnamed member the pairs will be added to the existing JSON
* object:
*
* <pre>
* {
* "p1": 1,
* "p2": 2
* }
* </pre>
* @param <E> the element type
* @param elements callback used to provide the elements
* @param extractor a {@link PairExtractor} used to extract the name/value pair
* @return a {@link Member} which may be configured further
* @see #usingExtractedPairs(BiConsumer, Function, Function)
* @see #usingPairs(BiConsumer)
*/
public <E> Member<T> usingExtractedPairs(BiConsumer<T, Consumer<E>> elements, PairExtractor<E> extractor) {
Assert.notNull(elements, "'elements' must not be null");
Assert.notNull(extractor, "'extractor' must not be null");
return usingExtractedPairs(elements, extractor::getName, extractor::getValue);
}
/**
* Add JSON name/value pairs by extracting values from a series of elements.
* Typically used with a {@link Iterable#forEach(Consumer)} call, for example:
*
* <pre class="code">
* members.add(Event::getTags).usingExtractedPairs(Iterable::forEach, Tag::getName, Tag::getValue);
* </pre>
* <p>
* When used with a named member, the pairs will be added as a new JSON value
* object:
*
* <pre>
* {
* "name": {
* "p1": 1,
* "p2": 2
* }
* }
* </pre>
*
* When used with an unnamed member the pairs will be added to the existing JSON
* object:
*
* <pre>
* {
* "p1": 1,
* "p2": 2
* }
* </pre>
* @param <E> the element type
* @param <N> the name type
* @param <V> the value type
* @param elements callback used to provide the elements
* @param nameExtractor {@link Function} used to extract the name
* @param valueExtractor {@link Function} used to extract the value
* @return a {@link Member} which may be configured further
* @see #usingExtractedPairs(BiConsumer, PairExtractor)
* @see #usingPairs(BiConsumer)
*/
public <E, N, V> Member<T> usingExtractedPairs(BiConsumer<T, Consumer<E>> elements,
Function<E, N> nameExtractor, Function<E, V> valueExtractor) {
Assert.notNull(elements, "'elements' must not be null");
Assert.notNull(nameExtractor, "'nameExtractor' must not be null");
Assert.notNull(valueExtractor, "'valueExtractor' must not be null");
return usingPairs((instance, pairsConsumer) -> elements.accept(instance, (element) -> {
N name = nameExtractor.apply(element);
V value = valueExtractor.apply(element);
pairsConsumer.accept(name, value);
}));
}
/**
* Add JSON name/value pairs. Typically used with a
* {@link Map#forEach(BiConsumer)} call, for example:
*
* <pre class="code">
* members.add(Event::getLabels).usingPairs(Map::forEach);
* </pre>
* <p>
* When used with a named member, the pairs will be added as a new JSON value
* object:
*
* <pre>
* {
* "name": {
* "p1": 1,
* "p2": 2
* }
* }
* </pre>
*
* When used with an unnamed member the pairs will be added to the existing JSON
* object:
*
* <pre>
* {
* "p1": 1,
* "p2": 2
* }
* </pre>
* @param <N> the name type
* @param <V> the value type
* @param pairs callback used to provide the pairs
* @return a {@link Member} which may be configured further
* @see #usingExtractedPairs(BiConsumer, PairExtractor)
* @see #usingPairs(BiConsumer)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public <N, V> Member<T> usingPairs(BiConsumer<T, BiConsumer<N, V>> pairs) {
Assert.notNull(pairs, "'pairs' must not be null");
Assert.state(this.pairs == null, "Pairs cannot be declared multiple times");
Assert.state(this.members == null, "Pairs cannot be declared when using members");
this.pairs = (BiConsumer) pairs;
return this;
}
/**
* Add JSON based on further {@link Members} configuration. For example:
*
* <pre class="code">
* members.add(User::getName).usingMembers((personMembers) -> {
* personMembers.add("first", Name::first);
* personMembers.add("last", Name::last);
* });
* </pre>
*
* <p>
* When used with a named member, the result will be added as a new JSON value
* object:
*
* <pre>
* {
* "name": {
* "first": "Jane",
* "last": "Doe"
* }
* }
* </pre>
*
* When used with an unnamed member the result will be added to the existing JSON
* object:
*
* <pre>
* {
* "first": "John",
* "last": "Doe"
* }
* </pre>
* @param members callback to configure the members
* @return a {@link Member} which may be configured further
* @see #usingExtractedPairs(BiConsumer, PairExtractor)
* @see #usingPairs(BiConsumer)
*/
public Member<T> usingMembers(Consumer<Members<T>> members) {
Assert.notNull(members, "'members' must not be null");
Assert.state(this.members == null, "Members cannot be declared multiple times");
Assert.state(this.pairs == null, "Members cannot be declared when using pairs");
this.members = new Members<>(members, this.name == null);
return this;
}
/**
* Writes the given instance using details configure by this member.
* @param instance the instance to write
* @param valueWriter the JSON value writer to use
*/
void write(@Nullable Object instance, JsonValueWriter valueWriter) {
T extracted = this.valueExtractor.extract(instance);
if (ValueExtractor.skip(extracted)) {
return;
}
Object value = getValueToWrite(extracted, valueWriter);
valueWriter.write(this.name, value);
}
private @Nullable Object getValueToWrite(@Nullable T extracted, JsonValueWriter valueWriter) {
WritableJson writableJson = getWritableJsonToWrite(extracted, valueWriter);
return (writableJson != null) ? WritableJson.of(writableJson) : extracted;
}
private @Nullable WritableJson getWritableJsonToWrite(@Nullable T extracted, JsonValueWriter valueWriter) {
BiConsumer<T, BiConsumer<?, ?>> pairs = this.pairs;
if (pairs != null) {
return (out) -> valueWriter.writePairs((outPairs) -> pairs.accept(extracted, outPairs));
}
Members<T> members = this.members;
if (members != null) {
return (out) -> members.write(extracted, valueWriter);
}
return null;
}
/**
* Whether this contributes one or more name/value pairs to the JSON.
* @return whether a name/value pair is contributed
*/
boolean contributesPair() {
return this.name != null || this.pairs != null || (this.members != null && this.members.contributesPair());
}
@Override
public String toString() {
return "Member at index " + this.index + ((this.name != null) ? "{%s}".formatted(this.name) : "");
}
/**
* Internal class used to manage member value extraction and filtering.
*
* @param <T> the member type
*/
@FunctionalInterface
interface ValueExtractor<T extends @Nullable Object> {
/**
* Represents a skipped value.
*/
Object SKIP = new Object();
/**
* Extract the value from the given instance.
* @param instance the source instance
* @return the extracted value or {@link #SKIP}
*/
@Nullable T extract(@Nullable Object instance);
/**
* Only extract when the given predicate matches.
* @param predicate the predicate to test
* @return a new {@link ValueExtractor}
*/
default ValueExtractor<T> when(Predicate<? super @Nullable T> predicate) {
return (instance) -> test(extract(instance), predicate);
}
@SuppressWarnings("unchecked")
private @Nullable T test(@Nullable T extracted, Predicate<? super @Nullable T> predicate) {
return (!skip(extracted) && predicate.test(extracted)) ? extracted : (T) SKIP;
}
/**
* Adapt the extracted value.
* @param <R> the result type
* @param extractor the extractor to use
* @return a new {@link ValueExtractor}
*/
default <R> ValueExtractor<R> as(Extractor<T, R> extractor) {
return (instance) -> apply(extract(instance), extractor);
}
@SuppressWarnings("unchecked")
private <R> @Nullable R apply(@Nullable T value, Extractor<T, R> extractor) {
if (skip(value)) {
return (R) SKIP;
}
return (value != null) ? extractor.extract(value) : null;
}
/**
* Create a new {@link ValueExtractor} based on the given {@link Function}.
* @param <S> the source type
* @param <T> the extracted type
* @param extractor the extractor to use
* @return a new {@link ValueExtractor} instance
*/
@SuppressWarnings("unchecked")
static <S, T> ValueExtractor<T> of(Extractor<S, T> extractor) {
return (instance) -> {
if (instance == null) {
return null;
}
return (skip(instance)) ? (T) SKIP : extractor.extract((S) instance);
};
}
/**
* Return if the extracted value should be skipped.
* @param <T> the value type
* @param extracted the value to test
* @return if the value is to be skipped
*/
static <T> boolean skip(@Nullable T extracted) {
return extracted == SKIP;
}
}
}
Domain
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free