Polish `JsonWriter` API

This commit is contained in:
Phillip Webb 2024-07-30 13:01:11 +01:00
parent 3e7412fe7e
commit fb72345491
7 changed files with 109 additions and 111 deletions

View File

@ -54,7 +54,7 @@ import org.springframework.util.StringUtils;
final class PulsarPropertiesMapper {
private static final JsonWriter<Map<String, String>> jsonWriter = JsonWriter
.of((members) -> members.addSelf().as(TreeMap::new).usingPairs(Map::forEach));
.of((members) -> members.add().as(TreeMap::new).usingPairs(Map::forEach));
private final PulsarProperties properties;

View File

@ -142,7 +142,7 @@ public interface JsonWriter<T> {
* @return a {@link JsonWriter} instance
*/
static <T> JsonWriter<T> standard() {
return of(Members::addSelf);
return of(Members::add);
}
/**
@ -306,8 +306,8 @@ public interface JsonWriter<T> {
* the various {@code add(...)} methods. Typically, members are declared with a
* {@code "name"} and a {@link Function} that will extract the value from the
* instance. Members can also be declared using a static value or a {@link Supplier}.
* The {@link #addSelf(String)} and {@link #addSelf()} methods may be used to access
* the actual instance being written.
* The {@link #add(String)} and {@link #add()} methods may be used to access the
* actual instance being written.
* <p>
* Members can be added without a {@code name} when a {@code Member.using(...)} method
* is used to complete the definition.
@ -343,7 +343,7 @@ public interface JsonWriter<T> {
* @param name the member name
* @return the added {@link Member} which may be configured further
*/
public Member<T> addSelf(String name) {
public Member<T> add(String name) {
return add(name, (instance) -> instance);
}
@ -389,46 +389,8 @@ public interface JsonWriter<T> {
* complete the configuration.
* @return the added {@link Member} which may be configured further
*/
public Member<T> addSelf() {
return add((instance) -> instance);
}
/**
* Add a new member with a static value. The member is added without a name, so
* one of the {@code Member.using(...)} methods must be used to complete the
* configuration.
* @param <V> the value type
* @param value the member value
* @return the added {@link Member} which may be configured further
*/
public <V> Member<V> add(V value) {
return add((instance) -> value);
}
/**
* Add a new member with a supplied value.The member is added without a name, so
* one of the {@code Member.using(...)} methods must be used to complete the
* configuration.
* @param <V> the value type
* @param supplier a supplier of the value
* @return the added {@link Member} which may be configured further
*/
public <V> Member<V> add(Supplier<V> supplier) {
Assert.notNull(supplier, "'supplier' must not be null");
return add((instance) -> supplier.get());
}
/**
* Add a new member with an extracted value. The member is added without a name,
* so one of the {@code Member.using(...)} methods must be used to complete the
* configuration.
* @param <V> the value type
* @param extractor a function to extract the value
* @return the added {@link Member} which may be configured further
*/
public <V> Member<V> add(Function<T, V> extractor) {
Assert.notNull(extractor, "'extractor' must not be null");
return addMember(null, extractor);
public Member<T> add() {
return from(Function.identity());
}
/**
@ -440,7 +402,42 @@ public interface JsonWriter<T> {
* @return the added {@link Member} which may be configured further
*/
public <M extends Map<K, V>, K, V> Member<M> addMapEntries(Function<T, M> extractor) {
return add(extractor).usingPairs(Map::forEach);
return from(extractor).usingPairs(Map::forEach);
}
/**
* Add members from a static value. One of the {@code Member.using(...)} methods
* must be used to complete the configuration.
* @param <V> the value type
* @param value the member value
* @return the added {@link Member} which may be configured further
*/
public <V> Member<V> from(V value) {
return from((instance) -> value);
}
/**
* Add members from a supplied value. One of the {@code Member.using(...)} methods
* must be used to complete the configuration.
* @param <V> the value type
* @param supplier a supplier of the value
* @return the added {@link Member} which may be configured further
*/
public <V> Member<V> from(Supplier<V> supplier) {
Assert.notNull(supplier, "'supplier' must not be null");
return from((instance) -> supplier.get());
}
/**
* Add members from an extracted value. One of the {@code Member.using(...)}
* methods must be used to complete the configuration.
* @param <V> the value type
* @param extractor a function to extract the value
* @return the added {@link Member} which may be configured further
*/
public <V> Member<V> from(Function<T, V> extractor) {
Assert.notNull(extractor, "'extractor' must not be null");
return addMember(null, extractor);
}
private <V> Member<V> addMember(String name, Function<T, V> extractor) {

View File

@ -53,10 +53,10 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF
service.jsonMembers(members);
members.add("log.logger", LogEvent::getLoggerName);
members.add("message", LogEvent::getMessage).as(Message::getFormattedMessage);
members.add(LogEvent::getContextData)
members.from(LogEvent::getContextData)
.whenNot(ReadOnlyStringMap::isEmpty)
.usingPairs((contextData, pairs) -> contextData.forEach(pairs::accept));
members.add(LogEvent::getThrownProxy).whenNotNull().usingMembers((thrownProxyMembers) -> {
members.from(LogEvent::getThrownProxy).whenNotNull().usingMembers((thrownProxyMembers) -> {
thrownProxyMembers.add("error.type", ThrowableProxy::getThrowable)
.whenNotNull()
.as(ObjectUtils::nullSafeClassName);

View File

@ -56,7 +56,7 @@ class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter<Lo
members.add("thread_name", LogEvent::getThreadName);
members.add("level", LogEvent::getLevel).as(Level::name);
members.add("level_value", LogEvent::getLevel).as(Level::intLevel);
members.add(LogEvent::getContextData)
members.from(LogEvent::getContextData)
.whenNot(ReadOnlyStringMap::isEmpty)
.usingPairs((contextData, pairs) -> contextData.forEach(pairs::accept));
members.add("tags", LogEvent::getMarker)

View File

@ -56,10 +56,10 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF
members.add("log.logger", ILoggingEvent::getLoggerName);
members.add("message", ILoggingEvent::getFormattedMessage);
members.addMapEntries(ILoggingEvent::getMDCPropertyMap);
members.add(ILoggingEvent::getKeyValuePairs)
members.from(ILoggingEvent::getKeyValuePairs)
.whenNotEmpty()
.usingExtractedPairs(Iterable::forEach, keyValuePairExtractor);
members.addSelf().whenNotNull(ILoggingEvent::getThrowableProxy).usingMembers((throwableMembers) -> {
members.add().whenNotNull(ILoggingEvent::getThrowableProxy).usingMembers((throwableMembers) -> {
throwableMembers.add("error.type", ILoggingEvent::getThrowableProxy).as(IThrowableProxy::getClassName);
throwableMembers.add("error.message", ILoggingEvent::getThrowableProxy).as(IThrowableProxy::getMessage);
throwableMembers.add("error.stack_trace", throwableProxyConverter::convert);

View File

@ -62,7 +62,7 @@ class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter<IL
members.add("level", ILoggingEvent::getLevel);
members.add("level_value", ILoggingEvent::getLevel).as(Level::toInt);
members.addMapEntries(ILoggingEvent::getMDCPropertyMap);
members.add(ILoggingEvent::getKeyValuePairs)
members.from(ILoggingEvent::getKeyValuePairs)
.whenNotEmpty()
.usingExtractedPairs(Iterable::forEach, keyValuePairExtractor);
members.add("tags", ILoggingEvent::getMarkerList)

View File

@ -89,51 +89,62 @@ public class JsonWriterTests {
@Test
void ofAddingNamedSelf() {
JsonWriter<Person> writer = JsonWriter.of((members) -> members.addSelf("test"));
JsonWriter<Person> writer = JsonWriter.of((members) -> members.add("test"));
assertThat(writer.writeToString(PERSON)).isEqualTo("""
{"test":"Spring Boot (10)"}""");
}
@Test
void ofAddingNamedValue() {
void ofAddingUnnamedSelf() {
JsonWriter<Person> writer = JsonWriter.of((members) -> members.add());
assertThat(writer.writeToString(PERSON)).isEqualTo(quoted("Spring Boot (10)"));
}
@Test
void ofAddValue() {
JsonWriter<Person> writer = JsonWriter.of((members) -> members.add("Spring", "Boot"));
assertThat(writer.writeToString(PERSON)).isEqualTo("""
{"Spring":"Boot"}""");
}
@Test
void ofAddingNamedSupplier() {
void ofAddSupplier() {
JsonWriter<Person> writer = JsonWriter.of((members) -> members.add("Spring", () -> "Boot"));
assertThat(writer.writeToString(PERSON)).isEqualTo("""
{"Spring":"Boot"}""");
}
@Test
void ofAddingUnnamedSelf() {
JsonWriter<Person> writer = JsonWriter.of((members) -> members.addSelf());
assertThat(writer.writeToString(PERSON)).isEqualTo(quoted("Spring Boot (10)"));
void ofAddExtractor() {
JsonWriter<Person> writer = JsonWriter.of((members) -> {
members.add("firstName", Person::firstName);
members.add("lastName", Person::lastName);
members.add("age", Person::age);
});
assertThat(writer.writeToString(PERSON)).isEqualTo("""
{"firstName":"Spring","lastName":"Boot","age":10}""");
}
@Test
void ofAddingUnnamedValue() {
JsonWriter<Person> writer = JsonWriter.of((members) -> members.add("Boot"));
void ofFromValue() {
JsonWriter<Person> writer = JsonWriter.of((members) -> members.from("Boot"));
assertThat(writer.writeToString(PERSON)).isEqualTo(quoted("Boot"));
}
@Test
void ofAddingUnnamedSupplier() {
JsonWriter<Person> writer = JsonWriter.of((members) -> members.add(() -> "Boot"));
void ofFromSupplier() {
JsonWriter<Person> writer = JsonWriter.of((members) -> members.from(() -> "Boot"));
assertThat(writer.writeToString(PERSON)).isEqualTo(quoted("Boot"));
}
@Test
void ofAddingUnnamedExtractor() {
JsonWriter<Person> writer = JsonWriter.of((members) -> members.add(Person::lastName));
void ofFromExtractor() {
JsonWriter<Person> writer = JsonWriter.of((members) -> members.from(Person::lastName));
assertThat(writer.writeToString(PERSON)).isEqualTo(quoted("Boot"));
}
@Test
void ofAddingMapEntries() {
void ofAddMapEntries() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("a", "A");
map.put("b", 123);
@ -144,17 +155,6 @@ public class JsonWriterTests {
{"a":"A","b":123,"c":true}""");
}
@Test
void ofAddingNamedExtractor() {
JsonWriter<Person> writer = JsonWriter.of((members) -> {
members.add("firstName", Person::firstName);
members.add("lastName", Person::lastName);
members.add("age", Person::age);
});
assertThat(writer.writeToString(PERSON)).isEqualTo("""
{"firstName":"Spring","lastName":"Boot","age":10}""");
}
@Test
void ofWhenNoMembersAddedThrowsException() {
assertThatIllegalStateException().isThrownBy(() -> JsonWriter.of((members) -> {
@ -165,7 +165,7 @@ public class JsonWriterTests {
void ofWhenOneContributesPairByNameAndOneHasNoNameThrowsException() {
assertThatIllegalStateException().isThrownBy(() -> JsonWriter.of((members) -> {
members.add("Spring", "Boot");
members.add("alone");
members.from("alone");
}))
.withMessage("Member at index 1 does not contribute a named pair, "
+ "ensure that all members have a name or call an appropriate 'using' method");
@ -174,8 +174,8 @@ public class JsonWriterTests {
@Test
void ofWhenOneContributesPairByUsingPairsAndOneHasNoNameThrowsException() {
assertThatIllegalStateException().isThrownBy(() -> JsonWriter.of((members) -> {
members.add(Map.of("Spring", "Boot")).usingPairs(Map::forEach);
members.add("alone");
members.from(Map.of("Spring", "Boot")).usingPairs(Map::forEach);
members.from("alone");
}))
.withMessage("Member at index 1 does not contribute a named pair, "
+ "ensure that all members have a name or call an appropriate 'using' method");
@ -184,11 +184,11 @@ public class JsonWriterTests {
@Test
void ofWhenOneContributesPairByUsingMembersAndOneHasNoNameThrowsException() {
assertThatIllegalStateException().isThrownBy(() -> JsonWriter.of((members) -> {
members.add(PERSON).usingMembers((personMembers) -> {
members.from(PERSON).usingMembers((personMembers) -> {
personMembers.add("first", Person::firstName);
personMembers.add("last", Person::firstName);
});
members.add("alone");
members.from("alone");
}))
.withMessage("Member at index 1 does not contribute a named pair, "
+ "ensure that all members have a name or call an appropriate 'using' method");
@ -235,7 +235,7 @@ public class JsonWriterTests {
@Test
void whenNotNull() {
JsonWriter<String> writer = JsonWriter.of((members) -> members.addSelf().whenNotNull());
JsonWriter<String> writer = JsonWriter.of((members) -> members.add().whenNotNull());
assertThat(writer.writeToString("test")).isEqualTo(quoted("test"));
assertThat(writer.writeToString(null)).isEmpty();
}
@ -243,14 +243,14 @@ public class JsonWriterTests {
@Test
void whenNotNullExtracted() {
Person personWithNull = new Person("Spring", null, 10);
JsonWriter<Person> writer = JsonWriter.of((members) -> members.addSelf().whenNotNull(Person::lastName));
JsonWriter<Person> writer = JsonWriter.of((members) -> members.add().whenNotNull(Person::lastName));
assertThat(writer.writeToString(PERSON)).isEqualTo(quoted("Spring Boot (10)"));
assertThat(writer.writeToString(personWithNull)).isEmpty();
}
@Test
void whenHasLength() {
JsonWriter<String> writer = JsonWriter.of((members) -> members.addSelf().whenHasLength());
JsonWriter<String> writer = JsonWriter.of((members) -> members.add().whenHasLength());
assertThat(writer.writeToString("test")).isEqualTo(quoted("test"));
assertThat(writer.writeToString("")).isEmpty();
assertThat(writer.writeToString(null)).isEmpty();
@ -258,7 +258,7 @@ public class JsonWriterTests {
@Test
void whenHasLengthOnNonString() {
JsonWriter<StringBuilder> writer = JsonWriter.of((members) -> members.addSelf().whenHasLength());
JsonWriter<StringBuilder> writer = JsonWriter.of((members) -> members.add().whenHasLength());
assertThat(writer.writeToString(new StringBuilder("test"))).isEqualTo(quoted("test"));
assertThat(writer.writeToString(new StringBuilder())).isEmpty();
assertThat(writer.writeToString(null)).isEmpty();
@ -266,7 +266,7 @@ public class JsonWriterTests {
@Test
void whenNotEmpty() {
JsonWriter<Object> writer = JsonWriter.of((members) -> members.addSelf().whenNotEmpty());
JsonWriter<Object> writer = JsonWriter.of((members) -> members.add().whenNotEmpty());
assertThat(writer.writeToString(List.of("a"))).isEqualTo("""
["a"]""");
assertThat(writer.writeToString(Collections.emptyList())).isEmpty();
@ -277,7 +277,7 @@ public class JsonWriterTests {
@Test
void whenNot() {
JsonWriter<List<String>> writer = JsonWriter.of((members) -> members.addSelf().whenNot(List::isEmpty));
JsonWriter<List<String>> writer = JsonWriter.of((members) -> members.add().whenNot(List::isEmpty));
assertThat(writer.writeToString(List.of("a"))).isEqualTo("""
["a"]""");
assertThat(writer.writeToString(Collections.emptyList())).isEmpty();
@ -285,7 +285,7 @@ public class JsonWriterTests {
@Test
void when() {
JsonWriter<List<String>> writer = JsonWriter.of((members) -> members.addSelf().when(List::isEmpty));
JsonWriter<List<String>> writer = JsonWriter.of((members) -> members.add().when(List::isEmpty));
assertThat(writer.writeToString(List.of("a"))).isEmpty();
assertThat(writer.writeToString(Collections.emptyList())).isEqualTo("[]");
}
@ -293,7 +293,7 @@ public class JsonWriterTests {
@Test
void chainedPredicates() {
Set<String> banned = Set.of("Spring", "Boot");
JsonWriter<String> writer = JsonWriter.of((members) -> members.addSelf()
JsonWriter<String> writer = JsonWriter.of((members) -> members.add()
.whenHasLength()
.whenNot(banned::contains)
.whenNot((string) -> string.length() <= 2));
@ -305,13 +305,13 @@ public class JsonWriterTests {
@Test
void as() {
JsonWriter<String> writer = JsonWriter.of((members) -> members.addSelf().as(Integer::valueOf));
JsonWriter<String> writer = JsonWriter.of((members) -> members.add().as(Integer::valueOf));
assertThat(writer.writeToString("123")).isEqualTo("123");
}
@Test
void asWhenValueIsNullDoesNotCallAdapter() {
JsonWriter<String> writer = JsonWriter.of((members) -> members.addSelf().as((value) -> {
JsonWriter<String> writer = JsonWriter.of((members) -> members.add().as((value) -> {
throw new RuntimeException("bad");
}));
writer.writeToString(null);
@ -321,7 +321,7 @@ public class JsonWriterTests {
void chainedAs() {
Function<Integer, Boolean> booleanAdapter = (integer) -> integer != 0;
JsonWriter<String> writer = JsonWriter
.of((members) -> members.addSelf().as(Integer::valueOf).as(booleanAdapter));
.of((members) -> members.add().as(Integer::valueOf).as(booleanAdapter));
assertThat(writer.writeToString("0")).isEqualTo("false");
assertThat(writer.writeToString("1")).isEqualTo("true");
}
@ -329,7 +329,7 @@ public class JsonWriterTests {
@Test
void chainedAsAndPredicates() {
Function<Integer, Boolean> booleanAdapter = (integer) -> integer != 0;
JsonWriter<String> writer = JsonWriter.of((members) -> members.addSelf()
JsonWriter<String> writer = JsonWriter.of((members) -> members.add()
.whenNot(String::isEmpty)
.as(Integer::valueOf)
.when((integer) -> integer < 2)
@ -348,7 +348,7 @@ public class JsonWriterTests {
PairExtractor<Map.Entry<String, Object>> extractor = PairExtractor.of(Map.Entry::getKey,
Map.Entry::getValue);
JsonWriter<Map<String, Object>> writer = JsonWriter
.of((members) -> members.addSelf().as(Map::entrySet).usingExtractedPairs(Set::forEach, extractor));
.of((members) -> members.add().as(Map::entrySet).usingExtractedPairs(Set::forEach, extractor));
assertThat(writer.writeToString(map)).isEqualTo("""
{"a":"A","b":"B"}""");
}
@ -360,7 +360,7 @@ public class JsonWriterTests {
map.put("b", "B");
Function<Map.Entry<String, Object>, String> nameExtractor = Map.Entry::getKey;
Function<Map.Entry<String, Object>, Object> valueExtractor = Map.Entry::getValue;
JsonWriter<Map<String, Object>> writer = JsonWriter.of((members) -> members.addSelf()
JsonWriter<Map<String, Object>> writer = JsonWriter.of((members) -> members.add()
.as(Map::entrySet)
.usingExtractedPairs(Set::forEach, nameExtractor, valueExtractor));
assertThat(writer.writeToString(map)).isEqualTo("""
@ -372,24 +372,23 @@ public class JsonWriterTests {
Map<String, Object> map = new LinkedHashMap<>();
map.put("a", "A");
map.put("b", "B");
JsonWriter<Map<String, Object>> writer = JsonWriter
.of((members) -> members.addSelf().usingPairs(Map::forEach));
JsonWriter<Map<String, Object>> writer = JsonWriter.of((members) -> members.add().usingPairs(Map::forEach));
assertThat(writer.writeToString(map)).isEqualTo("""
{"a":"A","b":"B"}""");
}
@Test
void usingPairsWhenAlreadyDeclaredThrowsException() {
assertThatIllegalStateException().isThrownBy(() -> JsonWriter
.of((members) -> members.add(Collections.emptyMap()).usingPairs(Map::forEach).usingPairs(Map::forEach)))
assertThatIllegalStateException().isThrownBy(() -> JsonWriter.of((
members) -> members.from(Collections.emptyMap()).usingPairs(Map::forEach).usingPairs(Map::forEach)))
.withMessage("Pairs cannot be declared multiple times");
}
@Test
void usingPairsWhenUsingMembersThrowsException() {
assertThatIllegalStateException()
.isThrownBy(() -> JsonWriter.of((members) -> members.add(Collections.emptyMap())
.usingMembers((mapMembers) -> mapMembers.addSelf("test"))
.isThrownBy(() -> JsonWriter.of((members) -> members.from(Collections.emptyMap())
.usingMembers((mapMembers) -> mapMembers.add("test"))
.usingPairs(Map::forEach)))
.withMessage("Pairs cannot be declared when using members");
}
@ -417,8 +416,10 @@ public class JsonWriterTests {
Couple couple = new Couple(PERSON, new Person("Spring", "Framework", 20));
JsonWriter<Couple> writer = JsonWriter.of((member) -> {
member.add("version", 1);
member.add(Couple::person1).usingMembers((personMembers) -> personMembers.add("one", Person::toString));
member.add(Couple::person2).usingMembers((personMembers) -> personMembers.add("two", Person::toString));
member.from(Couple::person1)
.usingMembers((personMembers) -> personMembers.add("one", Person::toString));
member.from(Couple::person2)
.usingMembers((personMembers) -> personMembers.add("two", Person::toString));
});
assertThat(writer.writeToString(couple)).isEqualTo("""
{"version":1,"one":"Spring Boot (10)","two":"Spring Framework (20)"}""");
@ -428,7 +429,7 @@ public class JsonWriterTests {
void usingMembersWithoutNameInMember() {
Couple couple = new Couple(PERSON, new Person("Spring", "Framework", 20));
JsonWriter<Couple> writer = JsonWriter.of((member) -> member.add("only", Couple::person2)
.usingMembers((personMembers) -> personMembers.add(Person::toString)));
.usingMembers((personMembers) -> personMembers.from(Person::toString)));
assertThat(writer.writeToString(couple)).isEqualTo("""
{"only":"Spring Framework (20)"}""");
}
@ -436,26 +437,26 @@ public class JsonWriterTests {
@Test
void usingMemebersWithoutNameAtAll() {
Couple couple = new Couple(PERSON, new Person("Spring", "Framework", 20));
JsonWriter<Couple> writer = JsonWriter.of((member) -> member.add(Couple::person2)
.usingMembers((personMembers) -> personMembers.add(Person::toString)));
JsonWriter<Couple> writer = JsonWriter.of((member) -> member.from(Couple::person2)
.usingMembers((personMembers) -> personMembers.from(Person::toString)));
assertThat(writer.writeToString(couple)).isEqualTo(quoted("Spring Framework (20)"));
}
@Test
void usingMembersWhenAlreadyDeclaredThrowsException() {
assertThatIllegalStateException()
.isThrownBy(() -> JsonWriter.of((members) -> members.add(Collections.emptyMap())
.usingMembers((mapMembers) -> mapMembers.addSelf("test"))
.usingMembers((mapMembers) -> mapMembers.addSelf("test"))))
.isThrownBy(() -> JsonWriter.of((members) -> members.from(Collections.emptyMap())
.usingMembers((mapMembers) -> mapMembers.add("test"))
.usingMembers((mapMembers) -> mapMembers.add("test"))))
.withMessage("Members cannot be declared multiple times");
}
@Test
void usingMembersWhenUsingPairsThrowsException() {
assertThatIllegalStateException()
.isThrownBy(() -> JsonWriter.of((members) -> members.add(Collections.emptyMap())
.isThrownBy(() -> JsonWriter.of((members) -> members.from(Collections.emptyMap())
.usingPairs(Map::forEach)
.usingMembers((mapMembers) -> mapMembers.addSelf("test"))))
.usingMembers((mapMembers) -> mapMembers.add("test"))))
.withMessage("Members cannot be declared when using pairs");
}