From 49b7c0bb1d65f298b0513c9466cbc0d168d7997b Mon Sep 17 00:00:00 2001 From: Choko Date: Fri, 9 Feb 2024 15:07:56 +0900 Subject: [PATCH] Add includingDefaultValueFields option with specific fields (#29) --- .../common/protobuf/json/DoWrite.java | 6 ++- .../protobuf/json/MessageMarshaller.java | 41 ++++++++++++++++++- .../protobuf/json/TypeSpecificMarshaller.java | 11 ++++- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/curioswitch/common/protobuf/json/DoWrite.java b/src/main/java/org/curioswitch/common/protobuf/json/DoWrite.java index a3f7bd9..42c1193 100644 --- a/src/main/java/org/curioswitch/common/protobuf/json/DoWrite.java +++ b/src/main/java/org/curioswitch/common/protobuf/json/DoWrite.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription.ForLoadedType; @@ -274,18 +275,21 @@ private enum LocalVariable implements VariableHandle { private final Class messageClass; private final Descriptor descriptor; private final boolean includeDefaults; + private final Set fieldsToAlwaysOutput; private final boolean printingEnumsAsInts; private final boolean sortingMapKeys; DoWrite( Message prototype, boolean includeDefaults, + Set fieldsToAlwaysOutput, boolean printingEnumsAsInts, boolean sortingMapKeys) { this.prototype = prototype; this.messageClass = prototype.getClass(); this.descriptor = prototype.getDescriptorForType(); this.includeDefaults = includeDefaults; + this.fieldsToAlwaysOutput = fieldsToAlwaysOutput; this.printingEnumsAsInts = printingEnumsAsInts; this.sortingMapKeys = sortingMapKeys; } @@ -335,7 +339,7 @@ public Size apply( // if (message.getBarCount() != 0) { // ... // } - if (!includeDefaults + if ((!includeDefaults && !fieldsToAlwaysOutput.contains(f)) // Only print one-of fields if they're actually set (the default of a one-of is an empty // one-of). || field.isInOneof() diff --git a/src/main/java/org/curioswitch/common/protobuf/json/MessageMarshaller.java b/src/main/java/org/curioswitch/common/protobuf/json/MessageMarshaller.java index 5dc961b..f9135ee 100644 --- a/src/main/java/org/curioswitch/common/protobuf/json/MessageMarshaller.java +++ b/src/main/java/org/curioswitch/common/protobuf/json/MessageMarshaller.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.core.util.DefaultIndenter; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.google.protobuf.Descriptors.Descriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import java.io.IOException; @@ -27,10 +28,13 @@ import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; import org.curioswitch.common.protobuf.json.WellKnownTypeMarshaller.AnyMarshaller; @@ -127,6 +131,7 @@ public SerializableString getEscapeSequence(int ch) { private final MarshallerRegistry registry; private final boolean includingDefaultValueFields; + private final Set fieldsToAlwaysOutput; private final boolean preservingProtoFieldNames; private final boolean omittingInsignificantWhitespace; private final boolean ignoringUnknownFields; @@ -137,6 +142,7 @@ private MessageMarshaller( MarshallerRegistry registry, boolean omittingInsignificantWhitespace, boolean includingDefaultValueFields, + Set fieldsToAlwaysOutput, boolean preservingProtoFieldNames, boolean ignoringUnknownFields, boolean printingEnumsAsInts, @@ -145,6 +151,7 @@ private MessageMarshaller( this.registry = registry; this.omittingInsignificantWhitespace = omittingInsignificantWhitespace; this.includingDefaultValueFields = includingDefaultValueFields; + this.fieldsToAlwaysOutput = fieldsToAlwaysOutput; this.preservingProtoFieldNames = preservingProtoFieldNames; this.ignoringUnknownFields = ignoringUnknownFields; this.printingEnumsAsInts = printingEnumsAsInts; @@ -289,6 +296,7 @@ public Builder toBuilder() { registry.getBuiltParsers(), omittingInsignificantWhitespace, includingDefaultValueFields, + fieldsToAlwaysOutput, preservingProtoFieldNames, ignoringUnknownFields, printingEnumsAsInts, @@ -308,6 +316,7 @@ List registeredPrototypes() { public static final class Builder { private boolean includingDefaultValueFields; + private Set fieldsToAlwaysOutput = Collections.emptySet(); private boolean preservingProtoFieldNames; private boolean omittingInsignificantWhitespace; private boolean ignoringUnknownFields; @@ -357,11 +366,35 @@ public Builder register(Class messageClass) { /** * Set whether unset fields will be serialized with their default values. Empty repeated fields - * and map fields will be printed as well. The new Printer clones all other configurations from - * the current. + * and map fields will be printed as well. */ public Builder includingDefaultValueFields(boolean includingDefaultValueFields) { this.includingDefaultValueFields = includingDefaultValueFields; + this.fieldsToAlwaysOutput = Collections.emptySet(); + return this; + } + + /** + * Sets fields which should always be serialized with their default values, even if unset. Empty + * repeated fields and map fields will be printed as well, if they match. Call {@link + * #includingDefaultValueFields} with {@code true} to unconditionally include all fields. + */ + public Builder includingDefaultValueFields(FieldDescriptor... fieldsToAlwaysOutput) { + requireNonNull(fieldsToAlwaysOutput, "fieldsToAlwaysOutput"); + includingDefaultValueFields(Arrays.asList(fieldsToAlwaysOutput)); + return this; + } + + /** + * Sets fields which should always be serialized with their default values, even if unset. Empty + * repeated fields and map fields will be printed as well, if they match. Call {@link + * #includingDefaultValueFields} with {@code true} to unconditionally include all fields. + */ + public Builder includingDefaultValueFields(Iterable fieldsToAlwaysOutput) { + requireNonNull(fieldsToAlwaysOutput, "fieldsToAlwaysOutput"); + this.includingDefaultValueFields = false; + this.fieldsToAlwaysOutput = new HashSet<>(); + fieldsToAlwaysOutput.forEach(this.fieldsToAlwaysOutput::add); return this; } @@ -461,6 +494,7 @@ public MessageMarshaller build() { TypeSpecificMarshaller.buildAndAdd( prototype, includingDefaultValueFields, + fieldsToAlwaysOutput, preservingProtoFieldNames, ignoringUnknownFields, printingEnumsAsInts, @@ -475,6 +509,7 @@ public MessageMarshaller build() { registry, omittingInsignificantWhitespace, includingDefaultValueFields, + fieldsToAlwaysOutput, preservingProtoFieldNames, ignoringUnknownFields, printingEnumsAsInts, @@ -495,6 +530,7 @@ private Builder() { Map> preBuiltParsers, boolean omittingInsignificantWhitespace, boolean includingDefaultValueFields, + Set fieldsToAlwaysOutput, boolean preservingProtoFieldNames, boolean ignoringUnknownFields, boolean printingEnumsAsInts, @@ -502,6 +538,7 @@ private Builder() { this.preBuiltParsers = preBuiltParsers; this.omittingInsignificantWhitespace = omittingInsignificantWhitespace; this.includingDefaultValueFields = includingDefaultValueFields; + this.fieldsToAlwaysOutput = fieldsToAlwaysOutput; this.preservingProtoFieldNames = preservingProtoFieldNames; this.ignoringUnknownFields = ignoringUnknownFields; this.printingEnumsAsInts = printingEnumsAsInts; diff --git a/src/main/java/org/curioswitch/common/protobuf/json/TypeSpecificMarshaller.java b/src/main/java/org/curioswitch/common/protobuf/json/TypeSpecificMarshaller.java index b78d7ee..708b2ce 100644 --- a/src/main/java/org/curioswitch/common/protobuf/json/TypeSpecificMarshaller.java +++ b/src/main/java/org/curioswitch/common/protobuf/json/TypeSpecificMarshaller.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import net.bytebuddy.ByteBuddy; import net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods; import net.bytebuddy.description.type.TypeDefinition; @@ -133,6 +134,7 @@ T getMarshalledPrototype() { static void buildAndAdd( T prototype, boolean includingDefaultValueFields, + Set fieldsToAlwaysOutput, boolean preservingProtoFieldNames, boolean ignoringUnknownFields, boolean printingEnumsAsInts, @@ -144,6 +146,7 @@ static void buildAndAdd( buildOrFindMarshaller( prototype, includingDefaultValueFields, + fieldsToAlwaysOutput, preservingProtoFieldNames, ignoringUnknownFields, printingEnumsAsInts, @@ -177,6 +180,7 @@ static void buildAndAdd( private static void buildOrFindMarshaller( T prototype, boolean includingDefaultValueFields, + Set fieldsToAlwaysOutput, boolean preservingProtoFieldNames, boolean ignoringUnknownFields, boolean printingEnumsAsInts, @@ -254,7 +258,11 @@ private static void buildOrFindMarshaller( .throwing(IOException.class) .intercept( new DoWrite( - prototype, includingDefaultValueFields, printingEnumsAsInts, sortingMapKeys)); + prototype, + includingDefaultValueFields, + fieldsToAlwaysOutput, + printingEnumsAsInts, + sortingMapKeys)); try { marshaller = buddy @@ -278,6 +286,7 @@ private static void buildOrFindMarshaller( buildOrFindMarshaller( nestedPrototype, includingDefaultValueFields, + fieldsToAlwaysOutput, preservingProtoFieldNames, ignoringUnknownFields, printingEnumsAsInts,