This is an automated email from the ASF dual-hosted git repository.

clesaec pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/master by this push:
     new 51851d49a Avro 3876 jackson util (#2529)
51851d49a is described below

commit 51851d49af3283cd31caecf6bdc7b3a4e973a5a6
Author: Christophe Le Saec <[email protected]>
AuthorDate: Thu Oct 5 14:14:01 2023 +0200

    Avro 3876 jackson util (#2529)
    
    * AVRO-3876: Jackson util symmetric
---
 .../main/java/org/apache/avro/io/JsonEncoder.java  |  2 +-
 .../apache/avro/util/internal/JacksonUtils.java    | 26 +++++++---
 .../java/org/apache/avro/TestSchemaBuilder.java    | 57 +++++++++++-----------
 .../java/org/apache/avro/io/TestBinaryDecoder.java | 25 ++++++++++
 .../java/org/apache/avro/io/TestJsonDecoder.java   | 26 ++++++++++
 .../avro/util/internal/TestJacksonUtils.java       | 36 ++++++++++++--
 6 files changed, 134 insertions(+), 38 deletions(-)

diff --git a/lang/java/avro/src/main/java/org/apache/avro/io/JsonEncoder.java 
b/lang/java/avro/src/main/java/org/apache/avro/io/JsonEncoder.java
index 7e3a67eb6..46fb6b5cb 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/io/JsonEncoder.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/io/JsonEncoder.java
@@ -208,7 +208,7 @@ public class JsonEncoder extends ParsingEncoder implements 
Parser.ActionHandler
   @Override
   public void writeFloat(float f) throws IOException {
     parser.advance(Symbol.FLOAT);
-    out.writeNumber(f);
+    out.writeNumber(f + 0d);
   }
 
   @Override
diff --git 
a/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java 
b/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java
index 1a822899f..02a3872d4 100644
--- 
a/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java
+++ 
b/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java
@@ -18,6 +18,7 @@
 package org.apache.avro.util.internal;
 
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.nio.charset.StandardCharsets;
@@ -75,7 +76,7 @@ public class JacksonUtils {
       }
       generator.writeEndArray();
     } else if (datum instanceof byte[]) { // bytes, fixed
-      generator.writeString(new String((byte[]) datum, 
StandardCharsets.ISO_8859_1));
+      generator.writeBinary((byte[]) datum);// writeString(new String((byte[]) 
datum, StandardCharsets.ISO_8859_1));
     } else if (datum instanceof CharSequence || datum instanceof Enum<?>) { // 
string, enum
       generator.writeString(datum.toString());
     } else if (datum instanceof Double) { // double
@@ -136,10 +137,23 @@ public class JacksonUtils {
         return jsonNode.asDouble();
       }
     } else if (jsonNode.isDouble() || jsonNode.isFloat()) {
-      if (schema == null || schema.getType().equals(Schema.Type.DOUBLE)) {
-        return jsonNode.asDouble();
-      } else if (schema.getType().equals(Schema.Type.FLOAT)) {
-        return (float) jsonNode.asDouble();
+      if (schema != null) {
+        if (schema.getType().equals(Schema.Type.DOUBLE)) {
+          return jsonNode.doubleValue();
+        } else if (schema.getType().equals(Schema.Type.FLOAT)) {
+          return jsonNode.floatValue();
+        }
+      } else if (jsonNode.isDouble()) {
+        return jsonNode.doubleValue();
+      } else {
+        return jsonNode.floatValue();
+      }
+    } else if (jsonNode.isBinary()) {
+      try {
+        return jsonNode.binaryValue();
+      } catch (IOException ex) {
+        // only for TextNode, so, can't happen with binaryNode.
+        throw new UncheckedIOException(ex);
       }
     } else if (jsonNode.isTextual()) {
       if (schema == null || schema.getType().equals(Schema.Type.STRING) || 
schema.getType().equals(Schema.Type.ENUM)) {
@@ -175,7 +189,7 @@ public class JacksonUtils {
 
   /**
    * Convert an object into a map
-   * 
+   *
    * @param datum The object
    * @return Its Map representation
    */
diff --git 
a/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java 
b/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java
index fcbaae655..293554e8e 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.node.NullNode;
 import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -111,14 +112,14 @@ public class TestSchemaBuilder {
     assertTrue(s.getObjectProp("intProp") instanceof Integer);
     assertTrue(s.getObjectProp("longProp") instanceof Long);
     assertEquals(Long.MAX_VALUE, s.getObjectProp("longProp"));
-    assertTrue(s.getObjectProp("floatProp") instanceof Double);
+    assertTrue(s.getObjectProp("floatProp") instanceof Float);
     // float converts to double
-    assertEquals(1.0d, s.getObjectProp("floatProp"));
+    assertEquals(1.0f, s.getObjectProp("floatProp"));
     assertTrue(s.getObjectProp("doubleProp") instanceof Double);
     assertEquals(Double.MAX_VALUE, s.getObjectProp("doubleProp"));
     // byte[] converts to string
-    assertTrue(s.getObjectProp("byteProp") instanceof String);
-    assertEquals("ABC", s.getObjectProp("byteProp"));
+    assertTrue(s.getObjectProp("byteProp") instanceof byte[]);
+    assertArrayEquals(new byte[] { 0x41, 0x42, 0x43 }, (byte[]) 
s.getObjectProp("byteProp"));
     assertTrue(s.getObjectProp("stringProp") instanceof String);
     assertEquals("abc", s.getObjectProp("stringProp"));
   }
@@ -141,14 +142,14 @@ public class TestSchemaBuilder {
     assertTrue(f.getObjectProp("intProp") instanceof Integer);
     assertTrue(f.getObjectProp("longProp") instanceof Long);
     assertEquals(Long.MAX_VALUE, f.getObjectProp("longProp"));
-    assertTrue(f.getObjectProp("floatProp") instanceof Double);
+    assertTrue(f.getObjectProp("floatProp") instanceof Float);
     // float converts to double
-    assertEquals(1.0d, f.getObjectProp("floatProp"));
+    assertEquals(1.0f, f.getObjectProp("floatProp"));
     assertTrue(f.getObjectProp("doubleProp") instanceof Double);
     assertEquals(Double.MAX_VALUE, f.getObjectProp("doubleProp"));
     // byte[] converts to string
-    assertTrue(f.getObjectProp("byteProp") instanceof String);
-    assertEquals("ABC", f.getObjectProp("byteProp"));
+    assertTrue(f.getObjectProp("byteProp") instanceof byte[]);
+    assertArrayEquals(new byte[] { 0x41, 0x42, 0x43 }, (byte[]) 
f.getObjectProp("byteProp"));
     assertTrue(f.getObjectProp("stringProp") instanceof String);
     assertEquals("abc", f.getObjectProp("stringProp"));
 
@@ -180,11 +181,11 @@ public class TestSchemaBuilder {
     assertEquals(true, iter.next());
     assertEquals(Integer.MAX_VALUE, iter.next());
     assertEquals(Long.MAX_VALUE, iter.next());
-    // float converts to double
-    assertEquals(1.0d, iter.next());
+
+    assertEquals(1.0f, iter.next());
     assertEquals(Double.MAX_VALUE, iter.next());
-    // byte[] converts to string
-    assertEquals("ABC", iter.next());
+
+    assertArrayEquals(new byte[] { 0x41, 0x42, 0x43 }, (byte[]) iter.next());
     assertEquals("abc", iter.next());
   }
 
@@ -215,11 +216,11 @@ public class TestSchemaBuilder {
     assertEquals(true, iter.next());
     assertEquals(Integer.MAX_VALUE, iter.next());
     assertEquals(Long.MAX_VALUE, iter.next());
-    // float converts to double
-    assertEquals(1.0d, iter.next());
+
+    assertEquals(1.0f, iter.next());
     assertEquals(Double.MAX_VALUE, iter.next());
-    // byte[] converts to string
-    assertEquals("ABC", iter.next());
+
+    assertArrayEquals(new byte[] { 0x41, 0x42, 0x43 }, (byte[]) iter.next());
     assertEquals("abc", iter.next());
   }
 
@@ -248,14 +249,14 @@ public class TestSchemaBuilder {
     assertEquals(Integer.MAX_VALUE, valueMap.get("intKey"));
     assertTrue(valueMap.get("longKey") instanceof Long);
     assertEquals(Long.MAX_VALUE, valueMap.get("longKey"));
-    // float converts to double
-    assertTrue(valueMap.get("floatKey") instanceof Double);
-    assertEquals(1.0d, valueMap.get("floatKey"));
+
+    assertTrue(valueMap.get("floatKey") instanceof Float);
+    assertEquals(1.0f, valueMap.get("floatKey"));
     assertTrue(valueMap.get("doubleKey") instanceof Double);
     assertEquals(Double.MAX_VALUE, valueMap.get("doubleKey"));
-    // byte[] converts to string
-    assertTrue(valueMap.get("byteKey") instanceof String);
-    assertEquals("ABC", valueMap.get("byteKey"));
+
+    assertTrue(valueMap.get("byteKey") instanceof byte[]);
+    assertArrayEquals("ABC".getBytes(StandardCharsets.UTF_8), (byte[]) 
valueMap.get("byteKey"));
     assertTrue(valueMap.get("stringKey") instanceof String);
     assertEquals("abc", valueMap.get("stringKey"));
   }
@@ -288,14 +289,14 @@ public class TestSchemaBuilder {
     assertEquals(Integer.MAX_VALUE, valueMap.get("intKey"));
     assertTrue(valueMap.get("longKey") instanceof Long);
     assertEquals(Long.MAX_VALUE, valueMap.get("longKey"));
-    // float converts to double
-    assertTrue(valueMap.get("floatKey") instanceof Double);
-    assertEquals(1.0d, valueMap.get("floatKey"));
+
+    assertTrue(valueMap.get("floatKey") instanceof Float);
+    assertEquals(1.0f, valueMap.get("floatKey"));
     assertTrue(valueMap.get("doubleKey") instanceof Double);
     assertEquals(Double.MAX_VALUE, valueMap.get("doubleKey"));
-    // byte[] converts to string
-    assertTrue(valueMap.get("byteKey") instanceof String);
-    assertEquals("ABC", valueMap.get("byteKey"));
+
+    assertTrue(valueMap.get("byteKey") instanceof byte[]);
+    assertEquals("ABC", new String((byte[]) valueMap.get("byteKey")));
     assertTrue(valueMap.get("stringKey") instanceof String);
     assertEquals("abc", valueMap.get("stringKey"));
   }
diff --git 
a/lang/java/avro/src/test/java/org/apache/avro/io/TestBinaryDecoder.java 
b/lang/java/avro/src/test/java/org/apache/avro/io/TestBinaryDecoder.java
index 6010fc9c6..805335857 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/io/TestBinaryDecoder.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/io/TestBinaryDecoder.java
@@ -20,8 +20,10 @@ package org.apache.avro.io;
 import org.apache.avro.AvroRuntimeException;
 import org.apache.avro.Schema;
 import org.apache.avro.SystemLimitException;
+import org.apache.avro.generic.GenericData;
 import org.apache.avro.generic.GenericDatumReader;
 import org.apache.avro.generic.GenericDatumWriter;
+import org.apache.avro.generic.GenericRecord;
 import org.apache.avro.util.ByteBufferInputStream;
 import org.apache.avro.util.ByteBufferOutputStream;
 import org.apache.avro.util.RandomData;
@@ -43,6 +45,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 
 import static org.apache.avro.TestSystemLimitException.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class TestBinaryDecoder {
   // prime number buffer size so that looping tests hit the buffer edge
@@ -682,4 +685,26 @@ public class TestBinaryDecoder {
     Assertions.assertThrows(EOFException.class, () -> d.readInt());
   }
 
+  @Test
+  void testFloatPrecision() throws Exception {
+    String def = "{\"type\":\"record\",\"name\":\"X\",\"fields\":" + 
"[{\"type\":\"float\",\"name\":\"n\"}]}";
+    Schema schema = new Schema.Parser().parse(def);
+    DatumReader<GenericRecord> reader = new GenericDatumReader<>(schema);
+
+    float value = 33.33000183105469f;
+
+    GenericData.Record record = new GenericData.Record(schema);
+    record.put(0, value);
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    Encoder encoder = EncoderFactory.get().directBinaryEncoder(out, null);
+
+    DatumWriter<GenericRecord> writer = new GenericDatumWriter<>(schema);
+    writer.write(record, encoder);
+    encoder.flush();
+
+    Decoder decoder = DecoderFactory.get().directBinaryDecoder(new 
ByteArrayInputStream(out.toByteArray()), null);
+    GenericRecord r = reader.read(null, decoder);
+    assertEquals(value + 0d, ((float) r.get("n")) + 0d);
+  }
+
 }
diff --git 
a/lang/java/avro/src/test/java/org/apache/avro/io/TestJsonDecoder.java 
b/lang/java/avro/src/test/java/org/apache/avro/io/TestJsonDecoder.java
index 050571396..1f44344e3 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/io/TestJsonDecoder.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/io/TestJsonDecoder.java
@@ -22,12 +22,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import org.apache.avro.AvroTypeException;
 import org.apache.avro.Schema;
 import org.apache.avro.SchemaBuilder;
+import org.apache.avro.generic.GenericData;
 import org.apache.avro.generic.GenericDatumReader;
+import org.apache.avro.generic.GenericDatumWriter;
 import org.apache.avro.generic.GenericRecord;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
 public class TestJsonDecoder {
@@ -66,6 +69,29 @@ public class TestJsonDecoder {
     }
   }
 
+  @Test
+  void testFloatPrecision() throws Exception {
+    String def = "{\"type\":\"record\",\"name\":\"X\",\"fields\":" + 
"[{\"type\":\"float\",\"name\":\"n\"}]}";
+    Schema schema = new Schema.Parser().parse(def);
+    DatumReader<GenericRecord> reader = new GenericDatumReader<>(schema);
+
+    float value = 33.33000183105469f;
+    GenericData.Record record = new GenericData.Record(schema);
+    record.put(0, value);
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    JsonEncoder encoder = EncoderFactory.get().jsonEncoder(schema, out);
+
+    DatumWriter<GenericRecord> writer = new GenericDatumWriter<>(schema);
+    writer.write(record, encoder);
+    encoder.flush();
+    // check the whole float precision is kept.
+    assertEquals("{\"n\":33.33000183105469}", out.toString());
+
+    Decoder decoder = DecoderFactory.get().jsonDecoder(schema, out.toString());
+    GenericRecord r = reader.read(null, decoder);
+    assertEquals(value + 0d, ((float) r.get("n")) + 0d);
+  }
+
   // Ensure that even if the order of fields in JSON is different from the 
order
   // in schema,
   // it works.
diff --git 
a/lang/java/avro/src/test/java/org/apache/avro/util/internal/TestJacksonUtils.java
 
b/lang/java/avro/src/test/java/org/apache/avro/util/internal/TestJacksonUtils.java
index cd6fe3f74..4a272ae5b 100644
--- 
a/lang/java/avro/src/test/java/org/apache/avro/util/internal/TestJacksonUtils.java
+++ 
b/lang/java/avro/src/test/java/org/apache/avro/util/internal/TestJacksonUtils.java
@@ -21,8 +21,10 @@ import static 
org.apache.avro.util.internal.JacksonUtils.toJsonNode;
 import static org.apache.avro.util.internal.JacksonUtils.toObject;
 import static org.junit.jupiter.api.Assertions.*;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.BigIntegerNode;
+import com.fasterxml.jackson.databind.node.BinaryNode;
 import com.fasterxml.jackson.databind.node.BooleanNode;
 import com.fasterxml.jackson.databind.node.DecimalNode;
 import com.fasterxml.jackson.databind.node.DoubleNode;
@@ -31,15 +33,24 @@ import com.fasterxml.jackson.databind.node.IntNode;
 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.fasterxml.jackson.databind.node.LongNode;
 import com.fasterxml.jackson.databind.node.NullNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.fasterxml.jackson.databind.node.TextNode;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
 import java.util.Collections;
+import java.util.stream.Stream;
+
 import org.apache.avro.JsonProperties;
 import org.apache.avro.Schema;
 import org.apache.avro.SchemaBuilder;
+
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 public class TestJacksonUtils {
 
@@ -55,8 +66,9 @@ public class TestJacksonUtils {
     assertEquals(IntNode.valueOf(1), toJsonNode(1));
     assertEquals(LongNode.valueOf(2), toJsonNode(2L));
     assertEquals(FloatNode.valueOf(1.0f), toJsonNode(1.0f));
-    assertEquals(DoubleNode.valueOf(2.0), toJsonNode(2.0));
-    assertEquals(TextNode.valueOf("\u0001\u0002"), toJsonNode(new byte[] { 1, 
2 }));
+    assertEquals(FloatNode.valueOf(33.33000183105469f), 
toJsonNode(33.33000183105469f));
+    assertEquals(DoubleNode.valueOf(2.0), toJsonNode(2.0d));
+    assertEquals(BinaryNode.valueOf(new byte[] { 1, 2 }), toJsonNode(new 
byte[] { 1, 2 }));
     assertEquals(TextNode.valueOf("a"), toJsonNode("a"));
     assertEquals(TextNode.valueOf("UP"), toJsonNode(Direction.UP));
     assertEquals(BigIntegerNode.valueOf(BigInteger.ONE), 
toJsonNode(BigInteger.ONE));
@@ -80,7 +92,7 @@ public class TestJacksonUtils {
     assertEquals(2L, toObject(IntNode.valueOf(2), 
Schema.create(Schema.Type.LONG)));
     assertEquals(1.0f, toObject(DoubleNode.valueOf(1.0), 
Schema.create(Schema.Type.FLOAT)));
     assertEquals(2.0, toObject(DoubleNode.valueOf(2.0)));
-    assertEquals(TextNode.valueOf("\u0001\u0002"), toJsonNode(new byte[] { 1, 
2 }));
+    assertEquals(BinaryNode.valueOf(new byte[] { 1, 2 }), toJsonNode(new 
byte[] { 1, 2 }));
     assertArrayEquals(new byte[] { 1, 2 },
         (byte[]) toObject(TextNode.valueOf("\u0001\u0002"), 
Schema.create(Schema.Type.BYTES)));
     assertEquals("a", toObject(TextNode.valueOf("a")));
@@ -102,4 +114,22 @@ public class TestJacksonUtils {
     assertEquals("a", toObject(TextNode.valueOf("a"), 
SchemaBuilder.unionOf().stringType().and().intType().endUnion()));
   }
 
+  @ParameterizedTest
+  @MethodSource("nodes")
+  void cycle(JsonNode input) {
+    Object object = JacksonUtils.toObject(input);
+    JsonNode node = JacksonUtils.toJsonNode(object);
+    Assertions.assertEquals(input, node);
+  }
+
+  public static Stream<Arguments> nodes() {
+    ObjectNode o1 = JsonNodeFactory.instance.objectNode();
+    o1.put("intField", 123);
+    o1.put("floatField", 33.33000183105469f);
+    o1.put("doubleField", 33.33000183105469245d);
+    return Stream.of(JsonNodeFactory.instance.numberNode(33.33000183105469f),
+        
JsonNodeFactory.instance.binaryNode("Hello".getBytes(StandardCharsets.ISO_8859_1)),
+        
JsonNodeFactory.instance.arrayNode().add(1).add("Hello").add(o1)).map(Arguments::of);
+  }
+
 }

Reply via email to