diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/PropertyNamingStrategyWrapper.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/PropertyNamingStrategyWrapper.java index 7a40f8d..f1de6c5 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/PropertyNamingStrategyWrapper.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/PropertyNamingStrategyWrapper.java @@ -8,18 +8,19 @@ @SuppressWarnings("serial") public class PropertyNamingStrategyWrapper { - private static final NamingBase SNAKE_TO_CAMEL = new SnakeToCamelNamingStrategy(); + static final NamingBase SNAKE_TO_CAMEL = new SnakeToCamelNamingStrategy(); private final NamingBase delegate; public PropertyNamingStrategyWrapper( Class messageType, - MapperConfig mapperConfig + MapperConfig mapperConfig, + ProtobufJacksonConfig protobufJacksonConfig ) { if (mapperConfig.getPropertyNamingStrategy() instanceof NamingBase) { this.delegate = (NamingBase) mapperConfig.getPropertyNamingStrategy(); } else { - this.delegate = SNAKE_TO_CAMEL; + this.delegate = protobufJacksonConfig.propertyNamingStrategy(); } } diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufJacksonConfig.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufJacksonConfig.java index b0b0476..d1b7a4c 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufJacksonConfig.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/ProtobufJacksonConfig.java @@ -1,6 +1,8 @@ package com.hubspot.jackson.datatype.protobuf; +import com.fasterxml.jackson.databind.PropertyNamingStrategies.NamingBase; import com.google.protobuf.ExtensionRegistry; +import com.hubspot.jackson.datatype.protobuf.internal.PropertyNamingCache; public class ProtobufJacksonConfig { @@ -8,21 +10,32 @@ public class ProtobufJacksonConfig { .builder() .build(); + public static class PropertyNamingStrategies { + + public static final NamingBase SNAKE_TO_CAMEL = + PropertyNamingStrategyWrapper.SNAKE_TO_CAMEL; + public static final NamingBase JSON_FORMAT = + PropertyNamingCache.JsonFormatPropertyNamingStrategy.INSTANCE; + } + private final ExtensionRegistryWrapper extensionRegistry; private final boolean acceptLiteralFieldnames; private final boolean properUnsignedNumberSerialization; private final boolean serializeLongsAsString; + private final NamingBase propertyNamingStrategy; private ProtobufJacksonConfig( ExtensionRegistryWrapper extensionRegistry, boolean acceptLiteralFieldnames, boolean properUnsignedNumberSerialization, - boolean serializeLongsAsString + boolean serializeLongsAsString, + NamingBase propertyNamingStrategy ) { this.extensionRegistry = extensionRegistry; this.acceptLiteralFieldnames = acceptLiteralFieldnames; this.properUnsignedNumberSerialization = properUnsignedNumberSerialization; this.serializeLongsAsString = serializeLongsAsString; + this.propertyNamingStrategy = propertyNamingStrategy; } public static ProtobufJacksonConfig getDefaultInstance() { @@ -49,12 +62,17 @@ public boolean serializeLongsAsString() { return serializeLongsAsString; } + public NamingBase propertyNamingStrategy() { + return propertyNamingStrategy; + } + public static class Builder { private ExtensionRegistryWrapper extensionRegistry = ExtensionRegistryWrapper.empty(); private boolean acceptLiteralFieldnames = false; private boolean properUnsignedNumberSerialization = false; private boolean serializeLongsAsString = false; + private NamingBase propertyNamingStrategy = PropertyNamingStrategies.SNAKE_TO_CAMEL; private Builder() {} @@ -71,6 +89,7 @@ public Builder useCanonicalSerialization() { acceptLiteralFieldnames(true); properUnsignedNumberSerialization(true); serializeLongsAsString(true); + propertyNamingStrategy(PropertyNamingStrategies.JSON_FORMAT); return this; } @@ -91,12 +110,21 @@ public Builder serializeLongsAsString(boolean serializeLongsAsString) { return this; } + public Builder propertyNamingStrategy(NamingBase propertyNamingStrategy) { + this.propertyNamingStrategy = + propertyNamingStrategy == null + ? PropertyNamingStrategies.SNAKE_TO_CAMEL + : propertyNamingStrategy; + return this; + } + public ProtobufJacksonConfig build() { return new ProtobufJacksonConfig( extensionRegistry, acceptLiteralFieldnames, properUnsignedNumberSerialization, - serializeLongsAsString + serializeLongsAsString, + propertyNamingStrategy ); } } diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/deserializers/MessageDeserializer.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/deserializers/MessageDeserializer.java index 7af0f6d..364ba62 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/deserializers/MessageDeserializer.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/builtin/deserializers/MessageDeserializer.java @@ -119,7 +119,8 @@ private Map buildExtensionLookup( ) { PropertyNamingStrategyWrapper namingStrategy = new PropertyNamingStrategyWrapper( messageType, - context.getConfig() + context.getConfig(), + config ); Map extensionLookup = new HashMap<>(); diff --git a/src/main/java/com/hubspot/jackson/datatype/protobuf/internal/PropertyNamingCache.java b/src/main/java/com/hubspot/jackson/datatype/protobuf/internal/PropertyNamingCache.java index b28c558..3395b70 100644 --- a/src/main/java/com/hubspot/jackson/datatype/protobuf/internal/PropertyNamingCache.java +++ b/src/main/java/com/hubspot/jackson/datatype/protobuf/internal/PropertyNamingCache.java @@ -1,5 +1,6 @@ package com.hubspot.jackson.datatype.protobuf.internal; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.google.common.collect.ImmutableMap; @@ -22,6 +23,20 @@ public class PropertyNamingCache { private final Map> serializationCache; private final Map> deserializationCache; + public static class JsonFormatPropertyNamingStrategy + extends PropertyNamingStrategies.NamingBase { + + public static final JsonFormatPropertyNamingStrategy INSTANCE = + new JsonFormatPropertyNamingStrategy(); + + private JsonFormatPropertyNamingStrategy() {} + + @Override + public String translate(String fieldName) { + return defaultJsonName(fieldName); + } + } + private PropertyNamingCache( Descriptor descriptor, Class messageType, @@ -86,7 +101,8 @@ private Function buildSerializationFunction( ) { PropertyNamingStrategyWrapper namingStrategy = new PropertyNamingStrategyWrapper( messageType, - mapperConfig + mapperConfig, + config ); Map tempMap = new HashMap<>(); @@ -107,7 +123,8 @@ private Function buildDeserializationFunction( ) { PropertyNamingStrategyWrapper namingStrategy = new PropertyNamingStrategyWrapper( messageType, - mapperConfig + mapperConfig, + config ); Map tempMap = new HashMap<>(); diff --git a/src/test/java/com/hubspot/jackson/datatype/protobuf/JsonFormatCompatibilityTest.java b/src/test/java/com/hubspot/jackson/datatype/protobuf/JsonFormatCompatibilityTest.java index b571009..b221de0 100644 --- a/src/test/java/com/hubspot/jackson/datatype/protobuf/JsonFormatCompatibilityTest.java +++ b/src/test/java/com/hubspot/jackson/datatype/protobuf/JsonFormatCompatibilityTest.java @@ -7,6 +7,7 @@ import com.hubspot.jackson.datatype.protobuf.util.ProtobufCreator; import com.hubspot.jackson.datatype.protobuf.util.TestProtobuf.AllFields; import com.hubspot.jackson.datatype.protobuf.util.TestProtobuf3.AllFieldsProto3; +import com.hubspot.jackson.datatype.protobuf.util.TestProtobuf3.UnconventionalProto3; import java.io.IOException; import org.junit.Test; @@ -81,6 +82,39 @@ public void jsonFormatSerializesAndWeDeserializeProto3() throws IOException { ); } + @Test + public void jsonFormatSerializesAndWeDeserializeUnconventionalProto3() + throws IOException { + repeat( + () -> { + UnconventionalProto3 original = ProtobufCreator.create( + UnconventionalProto3.class + ); + String json = JsonFormat.printer().print(original); + UnconventionalProto3 parsed = MAPPER.readValue(json, UnconventionalProto3.class); + assertThat(parsed).isEqualTo(original); + }, + 1_000 + ); + } + + @Test + public void weSerializeAndJsonFormatDeserializesUnconventionalProto3() + throws IOException { + repeat( + () -> { + UnconventionalProto3 original = ProtobufCreator.create( + UnconventionalProto3.class + ); + String json = MAPPER.writeValueAsString(original); + UnconventionalProto3.Builder builder = UnconventionalProto3.newBuilder(); + JsonFormat.parser().merge(json, builder); + assertThat(builder.build()).isEqualTo(original); + }, + 1_000 + ); + } + private interface Runnable { void run() throws IOException; } diff --git a/src/test/proto/test_proto3.proto b/src/test/proto/test_proto3.proto index 73a6a7c..b9d994d 100644 --- a/src/test/proto/test_proto3.proto +++ b/src/test/proto/test_proto3.proto @@ -87,3 +87,13 @@ message JsonNameProto3 { string lower_underscore = 3 [json_name = "lower_underscore"]; string different_name = 4 [json_name = "surprise!"]; } + +message UnconventionalProto3 { + string camelCase = 1; + string weird_UpperCamelCase = 2; + string weird_lowerCamelCase = 3; + string weird_1lowerCamelCase = 4; + string weird_2UpperCamelCase = 5; + string weird_3_lowerCamelCase = 6; + string weird_4_UpperCamelCase = 7; +} \ No newline at end of file