|
52 | 52 | import java.util.Map; |
53 | 53 |
|
54 | 54 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 55 | +import static org.junit.jupiter.api.Assertions.assertFalse; |
55 | 56 | import static org.junit.jupiter.api.Assertions.assertTrue; |
56 | 57 |
|
57 | 58 | class TestWriteJsonResult { |
@@ -631,4 +632,129 @@ void testChoiceArrayOfStringsOrArrayOfRecords() throws IOException { |
631 | 632 | final String output = new String(data, StandardCharsets.UTF_8); |
632 | 633 | assertEquals(json, output); |
633 | 634 | } |
| 635 | + |
| 636 | + @Test |
| 637 | + void testReuseInputSerializationDefaultTrueUsesFastPath() throws IOException { |
| 638 | + final List<RecordField> fields = new ArrayList<>(); |
| 639 | + fields.add(new RecordField("name", RecordFieldType.STRING.getDataType())); |
| 640 | + fields.add(new RecordField("age", RecordFieldType.INT.getDataType())); |
| 641 | + final RecordSchema schema = new SimpleRecordSchema(fields); |
| 642 | + |
| 643 | + final Map<String, Object> values = new HashMap<>(); |
| 644 | + values.put("name", "John Doe"); |
| 645 | + values.put("age", 42); |
| 646 | + |
| 647 | + final String rawForm = "{\"name\":\"John Doe\",\"age\":42,\"ignoredExtra\":\"preserved\"}"; |
| 648 | + final SerializedForm serializedForm = SerializedForm.of(rawForm, "application/json"); |
| 649 | + final Record record = new MapRecord(schema, values, serializedForm); |
| 650 | + |
| 651 | + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| 652 | + try (final WriteJsonResult writer = new WriteJsonResult(Mockito.mock(ComponentLog.class), schema, new SchemaNameAsAttribute(), baos, false, |
| 653 | + NullSuppression.NEVER_SUPPRESS, OutputGrouping.OUTPUT_ARRAY, RecordFieldType.DATE.getDefaultFormat(), |
| 654 | + RecordFieldType.TIME.getDefaultFormat(), RecordFieldType.TIMESTAMP.getDefaultFormat())) { |
| 655 | + writer.write(RecordSet.of(schema, record)); |
| 656 | + } |
| 657 | + |
| 658 | + final String output = baos.toString(StandardCharsets.UTF_8); |
| 659 | + assertTrue(output.contains("\"ignoredExtra\":\"preserved\""), |
| 660 | + "Default constructor preserves legacy fast-path behavior: raw bytes should be emitted verbatim"); |
| 661 | + } |
| 662 | + |
| 663 | + @Test |
| 664 | + void testReuseInputSerializationFalseForcesReserialization() throws IOException { |
| 665 | + final List<RecordField> fields = new ArrayList<>(); |
| 666 | + fields.add(new RecordField("name", RecordFieldType.STRING.getDataType())); |
| 667 | + fields.add(new RecordField("age", RecordFieldType.INT.getDataType())); |
| 668 | + final RecordSchema schema = new SimpleRecordSchema(fields); |
| 669 | + |
| 670 | + final Map<String, Object> values = new HashMap<>(); |
| 671 | + values.put("name", "John Doe"); |
| 672 | + values.put("age", 42); |
| 673 | + |
| 674 | + final String rawForm = "{\"name\":\"John Doe\",\"age\":42,\"ignoredExtra\":\"preserved\"}"; |
| 675 | + final SerializedForm serializedForm = SerializedForm.of(rawForm, "application/json"); |
| 676 | + final Record record = new MapRecord(schema, values, serializedForm); |
| 677 | + |
| 678 | + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| 679 | + try (final WriteJsonResult writer = new WriteJsonResult(Mockito.mock(ComponentLog.class), schema, new SchemaNameAsAttribute(), baos, false, |
| 680 | + NullSuppression.NEVER_SUPPRESS, OutputGrouping.OUTPUT_ARRAY, RecordFieldType.DATE.getDefaultFormat(), |
| 681 | + RecordFieldType.TIME.getDefaultFormat(), RecordFieldType.TIMESTAMP.getDefaultFormat(), |
| 682 | + "application/json", false, false)) { |
| 683 | + writer.write(RecordSet.of(schema, record)); |
| 684 | + } |
| 685 | + |
| 686 | + final String output = baos.toString(StandardCharsets.UTF_8); |
| 687 | + assertFalse(output.contains("ignoredExtra"), |
| 688 | + "When Reuse Input Serialization is false, the writer must re-serialize from typed values and ignore raw bytes"); |
| 689 | + assertEquals("[{\"name\":\"John Doe\",\"age\":42}]", output); |
| 690 | + } |
| 691 | + |
| 692 | + @Test |
| 693 | + void testReuseInputSerializationFalseHonorsTimestampFormat() throws IOException { |
| 694 | + final List<RecordField> fields = new ArrayList<>(); |
| 695 | + fields.add(new RecordField("event", RecordFieldType.TIMESTAMP.getDataType())); |
| 696 | + final RecordSchema schema = new SimpleRecordSchema(fields); |
| 697 | + |
| 698 | + final Timestamp eventTimestamp = Timestamp.valueOf("2025-03-20 17:33:11.000"); |
| 699 | + final Map<String, Object> values = new HashMap<>(); |
| 700 | + values.put("event", eventTimestamp); |
| 701 | + |
| 702 | + final String rawForm = "{\"event\":\"2025-03-20T17:33:11.000+0000\"}"; |
| 703 | + final SerializedForm serializedForm = SerializedForm.of(rawForm, "application/json"); |
| 704 | + final Record record = new MapRecord(schema, values, serializedForm); |
| 705 | + |
| 706 | + final ByteArrayOutputStream fastPathBaos = new ByteArrayOutputStream(); |
| 707 | + try (final WriteJsonResult writer = new WriteJsonResult(Mockito.mock(ComponentLog.class), schema, new SchemaNameAsAttribute(), fastPathBaos, false, |
| 708 | + NullSuppression.NEVER_SUPPRESS, OutputGrouping.OUTPUT_ARRAY, RecordFieldType.DATE.getDefaultFormat(), |
| 709 | + RecordFieldType.TIME.getDefaultFormat(), "yyyy-MM-dd'T'HH:mm:ss.SSSX", |
| 710 | + "application/json", false, true)) { |
| 711 | + writer.write(RecordSet.of(schema, record)); |
| 712 | + } |
| 713 | + |
| 714 | + assertTrue(fastPathBaos.toString(StandardCharsets.UTF_8).contains("+0000"), |
| 715 | + "With Reuse Input Serialization enabled, raw '+0000' form is passed through even though Timestamp Format would normalize to 'Z'"); |
| 716 | + |
| 717 | + final ByteArrayOutputStream slowPathBaos = new ByteArrayOutputStream(); |
| 718 | + try (final WriteJsonResult writer = new WriteJsonResult(Mockito.mock(ComponentLog.class), schema, new SchemaNameAsAttribute(), slowPathBaos, false, |
| 719 | + NullSuppression.NEVER_SUPPRESS, OutputGrouping.OUTPUT_ARRAY, RecordFieldType.DATE.getDefaultFormat(), |
| 720 | + RecordFieldType.TIME.getDefaultFormat(), "yyyy-MM-dd'T'HH:mm:ss.SSSX", |
| 721 | + "application/json", false, false)) { |
| 722 | + writer.write(RecordSet.of(schema, record)); |
| 723 | + } |
| 724 | + |
| 725 | + final String slowPathOutput = slowPathBaos.toString(StandardCharsets.UTF_8); |
| 726 | + assertFalse(slowPathOutput.contains("+0000"), |
| 727 | + "With Reuse Input Serialization disabled, writer's Timestamp Format must be applied even when SerializedForm is present"); |
| 728 | + assertTrue(slowPathOutput.contains("\"event\":\"2025-03-20T17:33:11.000"), |
| 729 | + "Re-serialized timestamp should reflect the configured format"); |
| 730 | + } |
| 731 | + |
| 732 | + @Test |
| 733 | + void testReuseInputSerializationFalseHonorsSuppressNulls() throws IOException { |
| 734 | + final List<RecordField> fields = new ArrayList<>(); |
| 735 | + fields.add(new RecordField("name", RecordFieldType.STRING.getDataType())); |
| 736 | + fields.add(new RecordField("middleName", RecordFieldType.STRING.getDataType())); |
| 737 | + final RecordSchema schema = new SimpleRecordSchema(fields); |
| 738 | + |
| 739 | + final Map<String, Object> values = new HashMap<>(); |
| 740 | + values.put("name", "John Doe"); |
| 741 | + values.put("middleName", null); |
| 742 | + |
| 743 | + final String rawForm = "{\"name\":\"John Doe\",\"middleName\":null}"; |
| 744 | + final SerializedForm serializedForm = SerializedForm.of(rawForm, "application/json"); |
| 745 | + final Record record = new MapRecord(schema, values, serializedForm); |
| 746 | + |
| 747 | + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| 748 | + try (final WriteJsonResult writer = new WriteJsonResult(Mockito.mock(ComponentLog.class), schema, new SchemaNameAsAttribute(), baos, false, |
| 749 | + NullSuppression.ALWAYS_SUPPRESS, OutputGrouping.OUTPUT_ARRAY, RecordFieldType.DATE.getDefaultFormat(), |
| 750 | + RecordFieldType.TIME.getDefaultFormat(), RecordFieldType.TIMESTAMP.getDefaultFormat(), |
| 751 | + "application/json", false, false)) { |
| 752 | + writer.write(RecordSet.of(schema, record)); |
| 753 | + } |
| 754 | + |
| 755 | + final String output = baos.toString(StandardCharsets.UTF_8); |
| 756 | + assertFalse(output.contains("middleName"), |
| 757 | + "Suppress Null Values must be honored when Reuse Input Serialization is false, even though the input JSON contained the null field"); |
| 758 | + assertEquals("[{\"name\":\"John Doe\"}]", output); |
| 759 | + } |
634 | 760 | } |
0 commit comments