Skip to content

Commit 9858b39

Browse files
committed
Added support for providing field name overrides when reading and writing JSON documents
1 parent 8ffbe6b commit 9858b39

File tree

11 files changed

+116
-18
lines changed

11 files changed

+116
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/target
2+
protobuf-codec.i*

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ http://maven.apache.org/maven-v4_0_0.xsd">
2222
<dependency>
2323
<groupId>com.google.protobuf</groupId>
2424
<artifactId>protobuf-java</artifactId>
25-
<version>2.3.0</version>
25+
<version>2.4.1</version>
2626
</dependency>
2727
<dependency>
2828
<groupId>org.codehaus.jackson</groupId>

protobuf-codec-core/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/target
2+
protobuf-codec-core.iml

protobuf-codec-core/src/main/java/protobuf/codec/AbstractCodec.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,51 @@ public static Builder mergeUnknownFieldsFromString(Builder builder,
177177
public Map<Feature, Object> getAllFeaturesSet() {
178178
return Collections.unmodifiableMap(featureMap);
179179
}
180+
181+
public static boolean stripFieldNameUnderscores(Map<Feature, Object> featureMap) {
182+
return Boolean.TRUE.equals(featureMap.get(Feature.STRIP_FIELD_NAME_UNDERSCORES));
183+
}
184+
185+
public static String stripFieldName(String fieldName, Map<Feature, Object> featureMap) {
186+
if (stripFieldNameUnderscores(featureMap) && fieldName.endsWith("_")) {
187+
String processed = fieldName;
188+
boolean modified = false;
189+
if (processed.startsWith("_")) {
190+
processed = processed.substring(1);
191+
modified = true;
192+
}
193+
if (processed.endsWith("_")) {
194+
processed = processed.substring(0, processed.length()-1);
195+
modified = true;
196+
}
197+
if (modified) {
198+
return stripFieldName(processed, featureMap);
199+
} else {
200+
return processed;
201+
}
202+
}
203+
return fieldName;
204+
}
205+
206+
public static String substituteFieldNameForWriting(String fieldName, Map<Feature, Object> featureMap) {
207+
return substituteFieldName(fieldName, false, featureMap);
208+
}
209+
public static String substituteFieldNameForReading(String fieldName, Map<Feature, Object> featureMap) {
210+
return substituteFieldName(fieldName, true, featureMap);
211+
}
212+
public static String substituteFieldName(String fieldName, boolean read, Map<Feature, Object> featureMap) {
213+
Map<String, String> aliases = Collections.EMPTY_MAP;
214+
if (read && featureMap.containsKey(Feature.FIELD_NAME_READ_SUBSTITUTES)) {
215+
aliases = (Map<String, String>) featureMap.get(Feature.FIELD_NAME_READ_SUBSTITUTES);
216+
} else if (featureMap.containsKey(Feature.FIELD_NAME_WRITE_SUBSTITUTES)) {
217+
aliases = (Map<String, String>) featureMap.get(Feature.FIELD_NAME_WRITE_SUBSTITUTES);
218+
}
219+
if (aliases.containsKey(fieldName)) {
220+
return aliases.get(fieldName);
221+
} else {
222+
return fieldName;
223+
}
224+
}
180225

181226

182227
/**
@@ -194,7 +239,7 @@ public static boolean isExtensionFieldName(String fieldName,Map<Feature, Object
194239
* Returns the field protobuf extn field name for the provided field name.
195240
* @param fieldName the provided field name
196241
* @return the protobuf field name = <field_name> for "extension_field_name>"
197-
* @throws IllegalArgumentException if {@link #isExtensionFieldName(String)} returns false
242+
* @throws IllegalArgumentException if {@link #isExtensionFieldName(String, java.util.Map)} returns false
198243
*/
199244
public static String parseExtensionFieldName(String fieldName,Map<Feature, Object> featureMap){
200245
if(!isExtensionFieldName(fieldName,featureMap)){

protobuf-codec-core/src/main/java/protobuf/codec/Codec.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ public interface Codec {
3838
* Write this Message {@link Message} to the provided output stream. The underlying stream is not closed by default,
3939
* user {@link Feature#CLOSE_STREAM} for the required settings.
4040
* The default values are not written out, the reason being that the client should be aware of the protobuf schema.
41-
* The {@link UnknownFieldSet} are written out on whether the {@link Feature#UNKNOWN_FIELDS} is set. By default, {@link UnknownFieldSet}
41+
* The {@link UnknownFieldSet} are written out on whether the {@link Feature#UNKNOWN_FIELD_ELEM_NAME} is set. By default, {@link UnknownFieldSet}
4242
* is written out Check the codec implementation on how the unknown field set is written out.
4343
* Extension fields are written out and the naming depends on the {@link Feature#EXTENSION_FIELD_NAME_PREFIX} set.
4444
* If an extension field not provided in the registry is encountered that field is skipped.
4545
* @param message the {@link Message}
46-
* @param os the output stream
46+
* @param writer the output stream writer
4747
* @throws IOException
4848
* @throws IllegalArgumentException if the provided message is not initialized
4949
*/
@@ -55,12 +55,12 @@ public interface Codec {
5555
* In case null values encountered are skipped since protobuf does not support null values yet
5656
* ( http://code.google.com/p/protobuf/issues/detail?id=57 )
5757
* The default values are not written out, the reason being that the client should be aware of the protobuf schema.
58-
* The {@link UnknownFieldSet} are read in depending on whether the {@link Feature#UNKNOWN_FIELDS} is set. By default, {@link UnknownFieldSet}
58+
* The {@link UnknownFieldSet} are read in depending on whether the {@link Feature#UNKNOWN_FIELD_ELEM_NAME} is set. By default, {@link UnknownFieldSet}
5959
* is read in. Check the codec implementation on how the unknown field need to be passed in.
6060
* Extension field names depend on {@link Feature#EXTENSION_FIELD_NAME_PREFIX}
6161
* If an extension field not provided in the registry is encountered that field is skipped.
6262
* @param messageType the {@link Class} corresponding to the {@link Message} the stream needs to be read into
63-
* @param in the input stream
63+
* @param reader the input stream reader
6464
* @return the {@link Message}
6565
* @throws IOException
6666
*/
@@ -76,7 +76,7 @@ public interface Codec {
7676
* is identified to be an extension field, but a corresponding mapping is not found in the registry, then
7777
* the field is skipped.
7878
* @param messageType the {@link Class} corresponding to the {@link Message} the stream needs to be read into
79-
* @param in the input stream
79+
* @param reader the input stream reader
8080
* @param extnRegistry the extension registry which contains the defn for extension fields.
8181
* @return the {@link Message}
8282
* @throws IOException
@@ -114,7 +114,7 @@ public interface Codec {
114114
* @param extnRegistry the extension registry
115115
* @return
116116
* @throws IOException
117-
* @see {@link #toMessage(Reader)}
117+
* @see {@link #toMessage(Class, Reader)}
118118
*/
119119
<T extends Message> T toMessage(Class<T> messageType,InputStream in,ExtensionRegistry extnRegistry) throws IOException;
120120

@@ -162,6 +162,11 @@ public enum Feature{
162162
/** Unknown field element name */
163163
UNKNOWN_FIELD_ELEM_NAME,
164164
/** Extension field name prefix */
165-
EXTENSION_FIELD_NAME_PREFIX;
165+
EXTENSION_FIELD_NAME_PREFIX,
166+
/** Strip leading and trailing underscores from field names */
167+
STRIP_FIELD_NAME_UNDERSCORES,
168+
/** Provide field name substitutes for reading and writing from/to a protobuf stream*/
169+
FIELD_NAME_READ_SUBSTITUTES,
170+
FIELD_NAME_WRITE_SUBSTITUTES;
166171
}
167172
}

protobuf-codec-json/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
/target
21
/target
2+
protobuf-codec-json.iml

protobuf-codec-json/src/main/java/protobuf/codec/json/JacksonJsonReader.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ private static Builder parseObject(Builder builder, JsonParser parser,ExtensionR
4949
JsonToken currToken = parser.getCurrentToken();
5050
assert (currToken.equals(JsonToken.FIELD_NAME));
5151
String fieldName = parser.getCurrentName();
52+
fieldName = AbstractCodec.stripFieldName(fieldName, featureMap);
53+
fieldName = AbstractCodec.substituteFieldNameForReading(fieldName, featureMap);
5254
FieldDescriptor field=null;
5355
if(JsonCodec.isExtensionFieldName(fieldName,featureMap)){
5456
fieldName=JsonCodec.parseExtensionFieldName(fieldName,featureMap);

protobuf-codec-json/src/main/java/protobuf/codec/json/JacksonJsonWriter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public static void generateJSONFields(Message message,JsonGenerator generator,Ma
4343
Map.Entry<FieldDescriptor, Object> record = iterator.next();
4444
FieldDescriptor field = record.getKey();
4545
String fieldName = field.isExtension() ?JsonCodec.getExtensionFieldName(field.getName(),featureMap): field.getName(); // If extn field? box
46+
fieldName = AbstractCodec.substituteFieldNameForWriting(fieldName, featureMap);
4647
Object value = record.getValue();
4748
if (field.isRepeated()) {
4849
generator.writeArrayFieldStart(fieldName);

protobuf-codec-json/src/main/java/protobuf/codec/json/JsonCodec.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.InputStream;
55
import java.io.Reader;
66
import java.io.Writer;
7+
import java.util.Map;
78

89
import org.codehaus.jackson.JsonFactory;
910
import org.codehaus.jackson.JsonGenerator;
@@ -21,8 +22,8 @@
2122
* Json-protobuf serializer/deserializer.Relies on jackson as the underlying parser.
2223
* Supports {@link UnknownFieldSet}, extension field names prefixed with {@link Feature#EXTENSION_FIELD_NAME_PREFIX} and a "-"/
2324
* ex: "extension-name" idenfies an extension file by type name
24-
* In case the {@link Feature#UNKNOWN_FIELDS} is enabled ( this feature is enabled by default) each json object in
25-
* could contain a field by name "unknownfields" or whatever value the {@value Feature#UNKNOWN_FIELD_ELEM_NAME} is
25+
* In case the {@link Feature#UNKNOWN_FIELD_ELEM_NAME} is enabled ( this feature is enabled by default) each json object in
26+
* could contain a field by name "unknownfields" or whatever value the {@value Feature.UNKNOWN_FIELD_ELEM_NAME} is
2627
* set to. The value of this field is hex encoded byte string.
2728
*
2829
* @author sijuv
@@ -37,18 +38,18 @@ public JsonCodec(){
3738

3839
/**
3940
* Fills in a message from the data from the stream, The stream is not closed once the message is read in, the extn field names
40-
* need to be boxed. {@link UnknownFieldSet} passed is passed in the field defined by {@value Feature#UNKNOWN_FIELD_ELEM_NAME}.
41+
* need to be boxed. {@link UnknownFieldSet} passed is passed in the field defined by {@value Feature.UNKNOWN_FIELD_ELEM_NAME}.
4142
* The {@link UnknownFieldSet} provided is parsed in only if the {@link Feature#SUPPORT_UNKNOWN_FIELDS} is enabled.
4243
* The value passed in as unknown field should be corresponding hex encoded byte[]
4344
* The {@link Feature#EXTENSION_FIELD_NAME_PREFIX} controlls how the extension field names need to be.
4445
* @param builder the message builder
4546
* @param reader the input stream,
4647
* @param extnRegistry the extension registry to use
4748
* @see Codec#toMessage(Class, Reader)
48-
* @see AbstractCodec#mergeUnknownFieldsFromHexString(Builder, ExtensionRegistry, String)
49+
* @see AbstractCodec#mergeUnknownFieldsFromString(Builder, ExtensionRegistry, String)
4950
*/
5051
@Override
51-
protected Message readFromStream(Builder builder, Reader reader,ExtensionRegistry extnRegistry)throws IOException {
52+
protected Message readFromStream(Builder builder, Reader reader,ExtensionRegistry extnRegistry)throws IOException {
5253
JsonFactory jsonFactory=new JsonFactory();
5354
JsonParser parser=jsonFactory.createJsonParser(reader);
5455
return JacksonJsonReader.parse(builder, parser,extnRegistry,getAllFeaturesSet());
@@ -58,12 +59,12 @@ protected Message readFromStream(Builder builder, Reader reader,ExtensionRegist
5859
* Writes out the messages as a json object the provided stream.
5960
* The stream is not closed once the message is written out. {@link UnknownFieldSet} is serialized out as a hex byte string.
6061
* @param message the provided protobuf message
61-
* @param os the output stream onto which the message is written to.
62-
* @see Codec#toMessage(InputStream)
62+
* @param writer the output stream writer onto which the message is written to.
63+
* @see Codec#toMessage(Class, InputStream)
6364
* Sample response:<br>
6465
* {"id":1,"name":"elton john","extension-bar":1,"extension-id":24,"extension-place":"london"}
6566
* bar and place are extension fields.
66-
* @see AbstractCodec#encodeUnknownFieldsToHexString(UnknownFieldSet)
67+
* @see AbstractCodec#encodeUnknownFieldsToString(UnknownFieldSet)
6768
*
6869
*/
6970
@Override
@@ -81,6 +82,7 @@ public void validateAndSetFeature(Feature feature, Object value) {
8182
case SUPPORT_UNKNOWN_FIELDS:
8283
case PRETTY_PRINT:
8384
case CLOSE_STREAM:
85+
case STRIP_FIELD_NAME_UNDERSCORES:
8486
if(!(Boolean.TRUE.equals(value)||Boolean.FALSE.equals(value))){
8587
throw new IllegalArgumentException(String.format("Unsupported value [%s] for feature [%s]",value,feature));
8688
}
@@ -92,6 +94,12 @@ public void validateAndSetFeature(Feature feature, Object value) {
9294
throw new IllegalArgumentException(String.format("Feature [%s] expected to be a non null string",feature));
9395
}
9496
break;
97+
case FIELD_NAME_READ_SUBSTITUTES:
98+
case FIELD_NAME_WRITE_SUBSTITUTES:
99+
if(value==null || !(Map.class).isAssignableFrom(value.getClass())) {
100+
throw new IllegalArgumentException(String.format("Feature [%s] expected to be a non null Map<String,String>", feature));
101+
}
102+
break;
95103
default:
96104
throw new IllegalArgumentException(String.format("Unsupported feature [%s]",feature));
97105
}

protobuf-codec-json/src/test/java/protobuf/codec/json/JsonCodecTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import java.io.StringReader;
88
import java.io.StringWriter;
99
import java.util.ArrayList;
10+
import java.util.HashMap;
11+
import java.util.Map;
1012

1113
import org.apache.commons.codec.binary.Base64;
1214
import org.codehaus.jackson.JsonFactory;
@@ -308,4 +310,36 @@ public void testExtensionObjects()throws Exception{
308310
assertEquals(foo1, foo2);
309311
}
310312

313+
@Test
314+
public void testForFieldNameReplacements() throws Exception {
315+
Foo obj = Foo.newBuilder().setId(1).setName("FooName").build();
316+
317+
StringWriter writer = new StringWriter();
318+
JsonGenerator generator = new JsonFactory().createJsonGenerator(writer);
319+
generator.writeStartObject();
320+
generator.writeNumberField("_id", 1);
321+
generator.writeStringField("name_", "FooName");
322+
generator.writeEndObject();
323+
generator.close();
324+
325+
Codec codec = new JsonCodec();
326+
Map<String, String> replaces = new HashMap<String, String>();
327+
replaces.put("name", "name_");
328+
replaces.put("id", "_id");
329+
codec.setFeature(Codec.Feature.FIELD_NAME_WRITE_SUBSTITUTES, replaces);
330+
replaces = new HashMap<String, String>();
331+
replaces.put("name_", "name");
332+
replaces.put("_id", "id");
333+
codec.setFeature(Codec.Feature.FIELD_NAME_READ_SUBSTITUTES, replaces);
334+
335+
StringWriter out = new StringWriter();
336+
codec.fromMessage(obj, out);
337+
338+
assertEquals(writer.toString(), out.toString());
339+
340+
Foo msg = codec.toMessage(Foo.class, new StringReader(out.toString()));
341+
342+
assertEquals(obj, msg);
343+
}
344+
311345
}

protobuf-codec-xml/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/target
2+
protobuf-codec-xml.iml

0 commit comments

Comments
 (0)