You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by cu...@apache.org on 2010/12/16 22:54:54 UTC

svn commit: r1050184 - in /avro/trunk: CHANGES.txt lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java lang/java/avro/src/test/java/org/apache/avro/generic/TestGenericData.java

Author: cutting
Date: Thu Dec 16 21:54:53 2010
New Revision: 1050184

URL: http://svn.apache.org/viewvc?rev=1050184&view=rev
Log:
AVRO-713. Java: Fix GenericData.Record#toString() to produce valid JSON for enum symbols.  Contributed by Jay Kreps.

Modified:
    avro/trunk/CHANGES.txt
    avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java
    avro/trunk/lang/java/avro/src/test/java/org/apache/avro/generic/TestGenericData.java

Modified: avro/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/avro/trunk/CHANGES.txt?rev=1050184&r1=1050183&r2=1050184&view=diff
==============================================================================
--- avro/trunk/CHANGES.txt (original)
+++ avro/trunk/CHANGES.txt Thu Dec 16 21:54:53 2010
@@ -98,6 +98,9 @@ Avro 1.5.0 (unreleased)
     AVRO-710. Java: Add bounds checking to GenericData.Array#get(int).
     (Bo Shi via cutting)
 
+    AVRO-713. Java: Fix GenericData.Record#toString() to produce valid
+    JSON for enum symbols. (Jay Kreps via cutting)    
+
 Avro 1.4.1 (13 October 2010)
 
   NEW FEATURES

Modified: avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java?rev=1050184&r1=1050183&r2=1050184&view=diff
==============================================================================
--- avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java (original)
+++ avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java Thu Dec 16 21:54:53 2010
@@ -310,9 +310,10 @@ public class GenericData {
           buffer.append(", ");
       }
       buffer.append("}");
-    } else if (datum instanceof CharSequence) {
+    } else if (datum instanceof CharSequence
+               || datum instanceof GenericEnumSymbol) {
       buffer.append("\"");
-      buffer.append(datum);                       // TODO: properly escape!
+      writeEscapedString(datum.toString(), buffer);
       buffer.append("\"");
     } else if (datum instanceof ByteBuffer) {
       buffer.append("{\"bytes\": \"");
@@ -324,6 +325,50 @@ public class GenericData {
       buffer.append(datum);
     }
   }
+  
+  /* Adapted from http://code.google.com/p/json-simple */
+  private void writeEscapedString(String string, StringBuilder builder) {
+    for(int i = 0; i < string.length(); i++){
+      char ch = string.charAt(i);
+      switch(ch){
+        case '"':
+          builder.append("\\\"");
+          break;
+        case '\\':
+          builder.append("\\\\");
+          break;
+        case '\b':
+          builder.append("\\b");
+          break;
+        case '\f':
+          builder.append("\\f");
+          break;
+        case '\n':
+          builder.append("\\n");
+          break;
+        case '\r':
+          builder.append("\\r");
+          break;
+        case '\t':
+          builder.append("\\t");
+          break;
+        case '/':
+          builder.append("\\/");
+          break;
+        default:
+          // Reference: http://www.unicode.org/versions/Unicode5.1.0/
+          if((ch>='\u0000' && ch<='\u001F') || (ch>='\u007F' && ch<='\u009F') || (ch>='\u2000' && ch<='\u20FF')){
+            String hex = Integer.toHexString(ch);
+            builder.append("\\u");
+            for(int j = 0; j < 4-builder.length(); j++)
+              builder.append('0');
+            builder.append(string.toUpperCase());
+          } else {
+            builder.append(ch);
+          }
+        }
+    }
+  }
 
   /** Create a schema given an example datum. */
   public Schema induce(Object datum) {

Modified: avro/trunk/lang/java/avro/src/test/java/org/apache/avro/generic/TestGenericData.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/test/java/org/apache/avro/generic/TestGenericData.java?rev=1050184&r1=1050183&r2=1050184&view=diff
==============================================================================
--- avro/trunk/lang/java/avro/src/test/java/org/apache/avro/generic/TestGenericData.java (original)
+++ avro/trunk/lang/java/avro/src/test/java/org/apache/avro/generic/TestGenericData.java Thu Dec 16 21:54:53 2010
@@ -17,6 +17,7 @@
  */
 package org.apache.avro.generic;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Collection;
@@ -31,6 +32,10 @@ import org.apache.avro.Schema.Field;
 import org.apache.avro.AvroRuntimeException;
 import org.apache.avro.Schema.Type;
 import org.apache.avro.util.Utf8;
+import org.codehaus.jackson.JsonFactory;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.map.ObjectMapper;
 
 import org.junit.Test;
 
@@ -141,4 +146,24 @@ public class TestGenericData {
       fail("Expected IndexOutOfBoundsException getting index 0 after clear()");
     } catch (IndexOutOfBoundsException e) {}
   }
+  
+  @Test
+  public void testToStringIsJson() throws JsonParseException, IOException {
+    Field stringField = new Field("string", Schema.create(Type.STRING), null, null);
+    Field enumField = new Field("enum", Schema.createEnum("my_enum", "doc", null, Arrays.asList("a", "b", "c")), null, null);
+    Schema schema = Schema.createRecord("my_record", "doc", "mytest", false);
+    schema.setFields(Arrays.asList(stringField, enumField));
+    
+    GenericRecord r = new GenericData.Record(schema);
+    r.put(stringField.name(), "hello\nthere\"\tyou}");
+    r.put(enumField.name(), new GenericData.EnumSymbol("a"));
+    
+    String json = r.toString();
+    JsonFactory factory = new JsonFactory();
+    JsonParser parser = factory.createJsonParser(json);
+    ObjectMapper mapper = new ObjectMapper();
+    
+    // will throw exception if string is not parsable json
+    mapper.readTree(parser);
+  }
 }