Browse files

Added support for providing field name overrides when reading and wri…

…ting JSON documents
  • Loading branch information...
1 parent 8ffbe6b commit 9858b3905a502496bd783e89dc86843cf50cd0cc @aantono aantono committed Jul 21, 2011
View
1 .gitignore
@@ -1 +1,2 @@
/target
+protobuf-codec.i*
View
2 pom.xml
@@ -22,7 +22,7 @@ http://maven.apache.org/maven-v4_0_0.xsd">
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
- <version>2.3.0</version>
+ <version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
View
1 protobuf-codec-core/.gitignore
@@ -1 +1,2 @@
/target
+protobuf-codec-core.iml
View
47 protobuf-codec-core/src/main/java/protobuf/codec/AbstractCodec.java
@@ -177,6 +177,51 @@ public static Builder mergeUnknownFieldsFromString(Builder builder,
public Map<Feature, Object> getAllFeaturesSet() {
return Collections.unmodifiableMap(featureMap);
}
+
+ public static boolean stripFieldNameUnderscores(Map<Feature, Object> featureMap) {
+ return Boolean.TRUE.equals(featureMap.get(Feature.STRIP_FIELD_NAME_UNDERSCORES));
+ }
+
+ public static String stripFieldName(String fieldName, Map<Feature, Object> featureMap) {
+ if (stripFieldNameUnderscores(featureMap) && fieldName.endsWith("_")) {
+ String processed = fieldName;
+ boolean modified = false;
+ if (processed.startsWith("_")) {
+ processed = processed.substring(1);
+ modified = true;
+ }
+ if (processed.endsWith("_")) {
+ processed = processed.substring(0, processed.length()-1);
+ modified = true;
+ }
+ if (modified) {
+ return stripFieldName(processed, featureMap);
+ } else {
+ return processed;
+ }
+ }
+ return fieldName;
+ }
+
+ public static String substituteFieldNameForWriting(String fieldName, Map<Feature, Object> featureMap) {
+ return substituteFieldName(fieldName, false, featureMap);
+ }
+ public static String substituteFieldNameForReading(String fieldName, Map<Feature, Object> featureMap) {
+ return substituteFieldName(fieldName, true, featureMap);
+ }
+ public static String substituteFieldName(String fieldName, boolean read, Map<Feature, Object> featureMap) {
+ Map<String, String> aliases = Collections.EMPTY_MAP;
+ if (read && featureMap.containsKey(Feature.FIELD_NAME_READ_SUBSTITUTES)) {
+ aliases = (Map<String, String>) featureMap.get(Feature.FIELD_NAME_READ_SUBSTITUTES);
+ } else if (featureMap.containsKey(Feature.FIELD_NAME_WRITE_SUBSTITUTES)) {
+ aliases = (Map<String, String>) featureMap.get(Feature.FIELD_NAME_WRITE_SUBSTITUTES);
+ }
+ if (aliases.containsKey(fieldName)) {
+ return aliases.get(fieldName);
+ } else {
+ return fieldName;
+ }
+ }
/**
@@ -194,7 +239,7 @@ public static boolean isExtensionFieldName(String fieldName,Map<Feature, Object
* Returns the field protobuf extn field name for the provided field name.
* @param fieldName the provided field name
* @return the protobuf field name = <field_name> for "extension_field_name>"
- * @throws IllegalArgumentException if {@link #isExtensionFieldName(String)} returns false
+ * @throws IllegalArgumentException if {@link #isExtensionFieldName(String, java.util.Map)} returns false
*/
public static String parseExtensionFieldName(String fieldName,Map<Feature, Object> featureMap){
if(!isExtensionFieldName(fieldName,featureMap)){
View
19 protobuf-codec-core/src/main/java/protobuf/codec/Codec.java
@@ -38,12 +38,12 @@
* Write this Message {@link Message} to the provided output stream. The underlying stream is not closed by default,
* user {@link Feature#CLOSE_STREAM} for the required settings.
* The default values are not written out, the reason being that the client should be aware of the protobuf schema.
- * The {@link UnknownFieldSet} are written out on whether the {@link Feature#UNKNOWN_FIELDS} is set. By default, {@link UnknownFieldSet}
+ * The {@link UnknownFieldSet} are written out on whether the {@link Feature#UNKNOWN_FIELD_ELEM_NAME} is set. By default, {@link UnknownFieldSet}
* is written out Check the codec implementation on how the unknown field set is written out.
* Extension fields are written out and the naming depends on the {@link Feature#EXTENSION_FIELD_NAME_PREFIX} set.
* If an extension field not provided in the registry is encountered that field is skipped.
* @param message the {@link Message}
- * @param os the output stream
+ * @param writer the output stream writer
* @throws IOException
* @throws IllegalArgumentException if the provided message is not initialized
*/
@@ -55,12 +55,12 @@
* In case null values encountered are skipped since protobuf does not support null values yet
* ( http://code.google.com/p/protobuf/issues/detail?id=57 )
* The default values are not written out, the reason being that the client should be aware of the protobuf schema.
- * The {@link UnknownFieldSet} are read in depending on whether the {@link Feature#UNKNOWN_FIELDS} is set. By default, {@link UnknownFieldSet}
+ * The {@link UnknownFieldSet} are read in depending on whether the {@link Feature#UNKNOWN_FIELD_ELEM_NAME} is set. By default, {@link UnknownFieldSet}
* is read in. Check the codec implementation on how the unknown field need to be passed in.
* Extension field names depend on {@link Feature#EXTENSION_FIELD_NAME_PREFIX}
* If an extension field not provided in the registry is encountered that field is skipped.
* @param messageType the {@link Class} corresponding to the {@link Message} the stream needs to be read into
- * @param in the input stream
+ * @param reader the input stream reader
* @return the {@link Message}
* @throws IOException
*/
@@ -76,7 +76,7 @@
* is identified to be an extension field, but a corresponding mapping is not found in the registry, then
* the field is skipped.
* @param messageType the {@link Class} corresponding to the {@link Message} the stream needs to be read into
- * @param in the input stream
+ * @param reader the input stream reader
* @param extnRegistry the extension registry which contains the defn for extension fields.
* @return the {@link Message}
* @throws IOException
@@ -114,7 +114,7 @@
* @param extnRegistry the extension registry
* @return
* @throws IOException
- * @see {@link #toMessage(Reader)}
+ * @see {@link #toMessage(Class, Reader)}
*/
<T extends Message> T toMessage(Class<T> messageType,InputStream in,ExtensionRegistry extnRegistry) throws IOException;
@@ -162,6 +162,11 @@
/** Unknown field element name */
UNKNOWN_FIELD_ELEM_NAME,
/** Extension field name prefix */
- EXTENSION_FIELD_NAME_PREFIX;
+ EXTENSION_FIELD_NAME_PREFIX,
+ /** Strip leading and trailing underscores from field names */
+ STRIP_FIELD_NAME_UNDERSCORES,
+ /** Provide field name substitutes for reading and writing from/to a protobuf stream*/
+ FIELD_NAME_READ_SUBSTITUTES,
+ FIELD_NAME_WRITE_SUBSTITUTES;
}
}
View
2 protobuf-codec-json/.gitignore
@@ -1,2 +1,2 @@
-/target
/target
+protobuf-codec-json.iml
View
2 protobuf-codec-json/src/main/java/protobuf/codec/json/JacksonJsonReader.java
@@ -49,6 +49,8 @@ private static Builder parseObject(Builder builder, JsonParser parser,ExtensionR
JsonToken currToken = parser.getCurrentToken();
assert (currToken.equals(JsonToken.FIELD_NAME));
String fieldName = parser.getCurrentName();
+ fieldName = AbstractCodec.stripFieldName(fieldName, featureMap);
+ fieldName = AbstractCodec.substituteFieldNameForReading(fieldName, featureMap);
FieldDescriptor field=null;
if(JsonCodec.isExtensionFieldName(fieldName,featureMap)){
fieldName=JsonCodec.parseExtensionFieldName(fieldName,featureMap);
View
1 protobuf-codec-json/src/main/java/protobuf/codec/json/JacksonJsonWriter.java
@@ -43,6 +43,7 @@ public static void generateJSONFields(Message message,JsonGenerator generator,Ma
Map.Entry<FieldDescriptor, Object> record = iterator.next();
FieldDescriptor field = record.getKey();
String fieldName = field.isExtension() ?JsonCodec.getExtensionFieldName(field.getName(),featureMap): field.getName(); // If extn field? box
+ fieldName = AbstractCodec.substituteFieldNameForWriting(fieldName, featureMap);
Object value = record.getValue();
if (field.isRepeated()) {
generator.writeArrayFieldStart(fieldName);
View
24 protobuf-codec-json/src/main/java/protobuf/codec/json/JsonCodec.java
@@ -4,6 +4,7 @@
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
+import java.util.Map;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
@@ -21,8 +22,8 @@
* Json-protobuf serializer/deserializer.Relies on jackson as the underlying parser.
* Supports {@link UnknownFieldSet}, extension field names prefixed with {@link Feature#EXTENSION_FIELD_NAME_PREFIX} and a "-"/
* ex: "extension-name" idenfies an extension file by type name
- * In case the {@link Feature#UNKNOWN_FIELDS} is enabled ( this feature is enabled by default) each json object in
- * could contain a field by name "unknownfields" or whatever value the {@value Feature#UNKNOWN_FIELD_ELEM_NAME} is
+ * In case the {@link Feature#UNKNOWN_FIELD_ELEM_NAME} is enabled ( this feature is enabled by default) each json object in
+ * could contain a field by name "unknownfields" or whatever value the {@value Feature.UNKNOWN_FIELD_ELEM_NAME} is
* set to. The value of this field is hex encoded byte string.
*
* @author sijuv
@@ -37,18 +38,18 @@ public JsonCodec(){
/**
* 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
- * need to be boxed. {@link UnknownFieldSet} passed is passed in the field defined by {@value Feature#UNKNOWN_FIELD_ELEM_NAME}.
+ * need to be boxed. {@link UnknownFieldSet} passed is passed in the field defined by {@value Feature.UNKNOWN_FIELD_ELEM_NAME}.
* The {@link UnknownFieldSet} provided is parsed in only if the {@link Feature#SUPPORT_UNKNOWN_FIELDS} is enabled.
* The value passed in as unknown field should be corresponding hex encoded byte[]
* The {@link Feature#EXTENSION_FIELD_NAME_PREFIX} controlls how the extension field names need to be.
* @param builder the message builder
* @param reader the input stream,
* @param extnRegistry the extension registry to use
* @see Codec#toMessage(Class, Reader)
- * @see AbstractCodec#mergeUnknownFieldsFromHexString(Builder, ExtensionRegistry, String)
+ * @see AbstractCodec#mergeUnknownFieldsFromString(Builder, ExtensionRegistry, String)
*/
@Override
- protected Message readFromStream(Builder builder, Reader reader,ExtensionRegistry extnRegistry)throws IOException {
+ protected Message readFromStream(Builder builder, Reader reader,ExtensionRegistry extnRegistry)throws IOException {
JsonFactory jsonFactory=new JsonFactory();
JsonParser parser=jsonFactory.createJsonParser(reader);
return JacksonJsonReader.parse(builder, parser,extnRegistry,getAllFeaturesSet());
@@ -58,12 +59,12 @@ protected Message readFromStream(Builder builder, Reader reader,ExtensionRegist
* Writes out the messages as a json object the provided stream.
* The stream is not closed once the message is written out. {@link UnknownFieldSet} is serialized out as a hex byte string.
* @param message the provided protobuf message
- * @param os the output stream onto which the message is written to.
- * @see Codec#toMessage(InputStream)
+ * @param writer the output stream writer onto which the message is written to.
+ * @see Codec#toMessage(Class, InputStream)
* Sample response:<br>
* {"id":1,"name":"elton john","extension-bar":1,"extension-id":24,"extension-place":"london"}
* bar and place are extension fields.
- * @see AbstractCodec#encodeUnknownFieldsToHexString(UnknownFieldSet)
+ * @see AbstractCodec#encodeUnknownFieldsToString(UnknownFieldSet)
*
*/
@Override
@@ -81,6 +82,7 @@ public void validateAndSetFeature(Feature feature, Object value) {
case SUPPORT_UNKNOWN_FIELDS:
case PRETTY_PRINT:
case CLOSE_STREAM:
+ case STRIP_FIELD_NAME_UNDERSCORES:
if(!(Boolean.TRUE.equals(value)||Boolean.FALSE.equals(value))){
throw new IllegalArgumentException(String.format("Unsupported value [%s] for feature [%s]",value,feature));
}
@@ -92,6 +94,12 @@ public void validateAndSetFeature(Feature feature, Object value) {
throw new IllegalArgumentException(String.format("Feature [%s] expected to be a non null string",feature));
}
break;
+ case FIELD_NAME_READ_SUBSTITUTES:
+ case FIELD_NAME_WRITE_SUBSTITUTES:
+ if(value==null || !(Map.class).isAssignableFrom(value.getClass())) {
+ throw new IllegalArgumentException(String.format("Feature [%s] expected to be a non null Map<String,String>", feature));
+ }
+ break;
default:
throw new IllegalArgumentException(String.format("Unsupported feature [%s]",feature));
}
View
34 protobuf-codec-json/src/test/java/protobuf/codec/json/JsonCodecTest.java
@@ -7,6 +7,8 @@
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.codehaus.jackson.JsonFactory;
@@ -308,4 +310,36 @@ public void testExtensionObjects()throws Exception{
assertEquals(foo1, foo2);
}
+ @Test
+ public void testForFieldNameReplacements() throws Exception {
+ Foo obj = Foo.newBuilder().setId(1).setName("FooName").build();
+
+ StringWriter writer = new StringWriter();
+ JsonGenerator generator = new JsonFactory().createJsonGenerator(writer);
+ generator.writeStartObject();
+ generator.writeNumberField("_id", 1);
+ generator.writeStringField("name_", "FooName");
+ generator.writeEndObject();
+ generator.close();
+
+ Codec codec = new JsonCodec();
+ Map<String, String> replaces = new HashMap<String, String>();
+ replaces.put("name", "name_");
+ replaces.put("id", "_id");
+ codec.setFeature(Codec.Feature.FIELD_NAME_WRITE_SUBSTITUTES, replaces);
+ replaces = new HashMap<String, String>();
+ replaces.put("name_", "name");
+ replaces.put("_id", "id");
+ codec.setFeature(Codec.Feature.FIELD_NAME_READ_SUBSTITUTES, replaces);
+
+ StringWriter out = new StringWriter();
+ codec.fromMessage(obj, out);
+
+ assertEquals(writer.toString(), out.toString());
+
+ Foo msg = codec.toMessage(Foo.class, new StringReader(out.toString()));
+
+ assertEquals(obj, msg);
+ }
+
}
View
1 protobuf-codec-xml/.gitignore
@@ -1 +1,2 @@
/target
+protobuf-codec-xml.iml

0 comments on commit 9858b39

Please sign in to comment.