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

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 74fc1aa3c03 NIFI-16177 Added Timestamp Representation property to JSON 
Record Writer (#11519)
74fc1aa3c03 is described below

commit 74fc1aa3c033569d4bb287a19e368e285cb3aaeb
Author: Pierre Villard <[email protected]>
AuthorDate: Wed Aug 12 22:36:13 2026 +0200

    NIFI-16177 Added Timestamp Representation property to JSON Record Writer 
(#11519)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../apache/nifi/json/TimestampRepresentation.java  | 50 +++++++++++
 .../java/org/apache/nifi/json/WriteJsonResult.java | 52 +++++++++---
 .../org/apache/nifi/json/JsonRecordSetWriter.java  | 20 ++++-
 .../apache/nifi/json/TestJsonRecordSetWriter.java  | 19 +++++
 .../org/apache/nifi/json/TestWriteJsonResult.java  | 99 ++++++++++++++++++++++
 5 files changed, 228 insertions(+), 12 deletions(-)

diff --git 
a/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-json-record-utils/src/main/java/org/apache/nifi/json/TimestampRepresentation.java
 
b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-json-record-utils/src/main/java/org/apache/nifi/json/TimestampRepresentation.java
new file mode 100644
index 00000000000..aa4705722d3
--- /dev/null
+++ 
b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-json-record-utils/src/main/java/org/apache/nifi/json/TimestampRepresentation.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.json;
+
+import org.apache.nifi.components.DescribedValue;
+
+public enum TimestampRepresentation implements DescribedValue {
+    AUTO("Automatic", "Uses the configured Timestamp Format when present, 
otherwise writes epoch milliseconds"),
+    FORMATTED_STRING("Formatted String", "Writes timestamps as strings using 
the configured Timestamp Format"),
+    EPOCH_MILLISECONDS("Epoch Milliseconds", "Writes timestamps as integer 
JSON numbers containing milliseconds since the Unix epoch"),
+    EPOCH_SECONDS("Epoch Seconds", "Writes timestamps as decimal JSON numbers 
containing seconds since the Unix epoch with millisecond precision");
+
+    private final String displayName;
+    private final String description;
+
+    TimestampRepresentation(final String displayName, final String 
description) {
+        this.displayName = displayName;
+        this.description = description;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    public String getValue() {
+        return name();
+    }
+}
diff --git 
a/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-json-record-utils/src/main/java/org/apache/nifi/json/WriteJsonResult.java
 
b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-json-record-utils/src/main/java/org/apache/nifi/json/WriteJsonResult.java
index 148e630ae8d..762c15d91cb 100644
--- 
a/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-json-record-utils/src/main/java/org/apache/nifi/json/WriteJsonResult.java
+++ 
b/nifi-extension-bundles/nifi-extension-utils/nifi-record-utils/nifi-json-record-utils/src/main/java/org/apache/nifi/json/WriteJsonResult.java
@@ -45,6 +45,7 @@ import 
org.apache.nifi.serialization.record.util.DataTypeUtils;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.sql.Time;
 import java.util.Map;
@@ -69,23 +70,34 @@ public class WriteJsonResult extends 
AbstractRecordSetWriter implements RecordSe
     private final boolean prettyPrint;
     private final boolean allowScientificNotation;
     private final boolean serializedInputHandlingEnabled;
+    private final TimestampRepresentation timestampRepresentation;
 
     private static final ObjectMapper objectMapper = new ObjectMapper();
 
     public WriteJsonResult(final ComponentLog logger, final RecordSchema 
recordSchema, final SchemaAccessWriter schemaAccess, final OutputStream out, 
final boolean prettyPrint,
             final NullSuppression nullSuppression, final OutputGrouping 
outputGrouping, final String dateFormat, final String timeFormat, final String 
timestampFormat) throws IOException {
-        this(logger, recordSchema, schemaAccess, out, prettyPrint, 
nullSuppression, outputGrouping, dateFormat, timeFormat, timestampFormat, 
"application/json", false, true);
+        this(logger, recordSchema, schemaAccess, out, prettyPrint, 
nullSuppression, outputGrouping, dateFormat, timeFormat, timestampFormat, 
"application/json", false, true,
+                TimestampRepresentation.AUTO);
     }
 
     public WriteJsonResult(final ComponentLog logger, final RecordSchema 
recordSchema, final SchemaAccessWriter schemaAccess, final OutputStream out, 
final boolean prettyPrint,
         final NullSuppression nullSuppression, final OutputGrouping 
outputGrouping, final String dateFormat, final String timeFormat, final String 
timestampFormat,
         final String mimeType, final boolean allowScientificNotation) throws 
IOException {
-        this(logger, recordSchema, schemaAccess, out, prettyPrint, 
nullSuppression, outputGrouping, dateFormat, timeFormat, timestampFormat, 
mimeType, allowScientificNotation, true);
+        this(logger, recordSchema, schemaAccess, out, prettyPrint, 
nullSuppression, outputGrouping, dateFormat, timeFormat, timestampFormat, 
mimeType, allowScientificNotation, true,
+                TimestampRepresentation.AUTO);
     }
 
     public WriteJsonResult(final ComponentLog logger, final RecordSchema 
recordSchema, final SchemaAccessWriter schemaAccess, final OutputStream out, 
final boolean prettyPrint,
         final NullSuppression nullSuppression, final OutputGrouping 
outputGrouping, final String dateFormat, final String timeFormat, final String 
timestampFormat,
         final String mimeType, final boolean allowScientificNotation, final 
boolean serializedInputHandlingEnabled) throws IOException {
+        this(logger, recordSchema, schemaAccess, out, prettyPrint, 
nullSuppression, outputGrouping, dateFormat, timeFormat, timestampFormat, 
mimeType, allowScientificNotation,
+                serializedInputHandlingEnabled, TimestampRepresentation.AUTO);
+    }
+
+    public WriteJsonResult(final ComponentLog logger, final RecordSchema 
recordSchema, final SchemaAccessWriter schemaAccess, final OutputStream out, 
final boolean prettyPrint,
+        final NullSuppression nullSuppression, final OutputGrouping 
outputGrouping, final String dateFormat, final String timeFormat, final String 
timestampFormat,
+        final String mimeType, final boolean allowScientificNotation, final 
boolean serializedInputHandlingEnabled,
+        final TimestampRepresentation timestampRepresentation) throws 
IOException {
 
         super(out);
         this.logger = logger;
@@ -96,6 +108,7 @@ public class WriteJsonResult extends AbstractRecordSetWriter 
implements RecordSe
         this.mimeType = mimeType;
         this.allowScientificNotation = allowScientificNotation;
         this.serializedInputHandlingEnabled = serializedInputHandlingEnabled;
+        this.timestampRepresentation = timestampRepresentation;
 
         this.dateFormat = dateFormat;
         this.timeFormat = timeFormat;
@@ -199,7 +212,7 @@ public class WriteJsonResult extends 
AbstractRecordSetWriter implements RecordSe
      * this writer with {@code serializedInputHandlingEnabled = false}.
      */
     private boolean isUseSerializeForm(final Record record, final RecordSchema 
writeSchema) {
-        if (!serializedInputHandlingEnabled) {
+        if (!serializedInputHandlingEnabled || timestampRepresentation != 
TimestampRepresentation.AUTO) {
             return false;
         }
 
@@ -348,8 +361,7 @@ public class WriteJsonResult extends 
AbstractRecordSetWriter implements RecordSe
             return;
         }
         if (value instanceof java.util.Date) {
-            final Object formatted = 
STRING_FIELD_CONVERTER.convertField(value, 
Optional.ofNullable(timestampFormat), fieldName);
-            generator.writeObject(formatted);
+            writeTimestamp(generator, value, fieldName);
             return;
         }
         if (!allowScientificNotation) {
@@ -404,12 +416,7 @@ public class WriteJsonResult extends 
AbstractRecordSetWriter implements RecordSe
                 break;
             }
             case TIMESTAMP: {
-                final String stringValue = 
STRING_FIELD_CONVERTER.convertField(coercedValue, 
Optional.ofNullable(timestampFormat), fieldName);
-                if (DataTypeUtils.isLongTypeCompatible(stringValue)) {
-                    generator.writeNumber(DataTypeUtils.toLong(coercedValue, 
fieldName));
-                } else {
-                    generator.writeString(stringValue);
-                }
+                writeTimestamp(generator, coercedValue, fieldName);
                 break;
             }
             case DOUBLE:
@@ -502,6 +509,29 @@ public class WriteJsonResult extends 
AbstractRecordSetWriter implements RecordSe
         generator.writeEndArray();
     }
 
+    private void writeTimestamp(final JsonGenerator generator, final Object 
value, final String fieldName) throws IOException {
+        switch (timestampRepresentation) {
+            case FORMATTED_STRING:
+                
generator.writeString(STRING_FIELD_CONVERTER.convertField(value, 
Optional.ofNullable(timestampFormat), fieldName));
+                break;
+            case EPOCH_MILLISECONDS:
+                generator.writeNumber(DataTypeUtils.toLong(value, fieldName));
+                break;
+            case EPOCH_SECONDS:
+                
generator.writeNumber(BigDecimal.valueOf(DataTypeUtils.toLong(value, 
fieldName), 3));
+                break;
+            case AUTO:
+            default:
+                final String stringValue = 
STRING_FIELD_CONVERTER.convertField(value, 
Optional.ofNullable(timestampFormat), fieldName);
+                if (DataTypeUtils.isLongTypeCompatible(stringValue)) {
+                    generator.writeNumber(DataTypeUtils.toLong(value, 
fieldName));
+                } else {
+                    generator.writeString(stringValue);
+                }
+                break;
+        }
+    }
+
     @Override
     public String getMimeType() {
         return this.mimeType;
diff --git 
a/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonRecordSetWriter.java
 
b/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonRecordSetWriter.java
index d3062384e45..9af971ef31d 100644
--- 
a/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonRecordSetWriter.java
+++ 
b/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/main/java/org/apache/nifi/json/JsonRecordSetWriter.java
@@ -33,6 +33,7 @@ import org.apache.nifi.logging.ComponentLog;
 import org.apache.nifi.migration.PropertyConfiguration;
 import org.apache.nifi.schema.access.SchemaNotFoundException;
 import org.apache.nifi.serialization.DateTimeTextRecordSetWriter;
+import org.apache.nifi.serialization.DateTimeUtils;
 import org.apache.nifi.serialization.RecordSetWriter;
 import org.apache.nifi.serialization.RecordSetWriterFactory;
 import org.apache.nifi.serialization.record.RecordSchema;
@@ -143,6 +144,14 @@ public class JsonRecordSetWriter extends 
DateTimeTextRecordSetWriter implements
             .defaultValue(HANDLING_ENABLED.getValue())
             .required(true)
             .build();
+    public static final PropertyDescriptor TIMESTAMP_REPRESENTATION = new 
PropertyDescriptor.Builder()
+            .name("Timestamp Representation")
+            .description("Specifies how Timestamp logical fields are 
represented in JSON. Date and Time logical fields are not affected.")
+            .expressionLanguageSupported(ExpressionLanguageScope.NONE)
+            .allowableValues(TimestampRepresentation.class)
+            .defaultValue(TimestampRepresentation.AUTO.getValue())
+            .required(true)
+            .build();
 
     private volatile boolean prettyPrint;
     private volatile boolean allowScientificNotation;
@@ -151,6 +160,7 @@ public class JsonRecordSetWriter extends 
DateTimeTextRecordSetWriter implements
     private volatile String compressionFormat;
     private volatile int compressionLevel;
     private volatile boolean serializedInputHandlingEnabled;
+    private volatile TimestampRepresentation timestampRepresentation;
 
     @Override
     protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
@@ -162,6 +172,7 @@ public class JsonRecordSetWriter extends 
DateTimeTextRecordSetWriter implements
         properties.add(COMPRESSION_FORMAT);
         properties.add(COMPRESSION_LEVEL);
         properties.add(SERIALIZED_JSON_INPUT_HANDLING);
+        properties.add(TIMESTAMP_REPRESENTATION);
         return properties;
     }
 
@@ -189,6 +200,11 @@ public class JsonRecordSetWriter extends 
DateTimeTextRecordSetWriter implements
             problems.add(new ValidationResult.Builder().input("Pretty 
Print").valid(false)
                     .explanation("Pretty Print JSON must be false when 'Output 
Grouping' is set to 'One Line Per Object'").build());
         }
+        if 
(context.getProperty(TIMESTAMP_REPRESENTATION).asAllowableValue(TimestampRepresentation.class)
 == TimestampRepresentation.FORMATTED_STRING
+                && 
!context.getProperty(DateTimeUtils.TIMESTAMP_FORMAT).isSet()) {
+            problems.add(new 
ValidationResult.Builder().subject(TIMESTAMP_REPRESENTATION.getDisplayName()).input(TimestampRepresentation.FORMATTED_STRING.getDisplayName()).valid(false)
+                    .explanation("Timestamp Format must be configured when 
Timestamp Representation is set to Formatted String").build());
+        }
         return problems;
     }
 
@@ -220,6 +236,7 @@ public class JsonRecordSetWriter extends 
DateTimeTextRecordSetWriter implements
         this.compressionFormat = 
context.getProperty(COMPRESSION_FORMAT).getValue();
         this.compressionLevel = 
context.getProperty(COMPRESSION_LEVEL).asInteger();
         this.serializedInputHandlingEnabled = 
HANDLING_ENABLED.getValue().equals(context.getProperty(SERIALIZED_JSON_INPUT_HANDLING).getValue());
+        this.timestampRepresentation = 
context.getProperty(TIMESTAMP_REPRESENTATION).asAllowableValue(TimestampRepresentation.class);
     }
 
     @Override
@@ -264,7 +281,8 @@ public class JsonRecordSetWriter extends 
DateTimeTextRecordSetWriter implements
         }
 
         return new WriteJsonResult(logger, schema, 
getSchemaAccessWriter(schema, variables), compressionOut, prettyPrint, 
nullSuppression, outputGrouping,
-                getDateFormat().orElse(null), getTimeFormat().orElse(null), 
getTimestampFormat().orElse(null), mimeType, allowScientificNotation, 
serializedInputHandlingEnabled);
+                getDateFormat().orElse(null), getTimeFormat().orElse(null), 
getTimestampFormat().orElse(null), mimeType, allowScientificNotation, 
serializedInputHandlingEnabled,
+                timestampRepresentation);
     }
 
 }
diff --git 
a/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestJsonRecordSetWriter.java
 
b/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestJsonRecordSetWriter.java
index 82d480027da..23a54c69eee 100644
--- 
a/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestJsonRecordSetWriter.java
+++ 
b/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestJsonRecordSetWriter.java
@@ -16,10 +16,15 @@
  */
 package org.apache.nifi.json;
 
+import org.apache.nifi.reporting.InitializationException;
 import org.apache.nifi.schema.access.SchemaAccessUtils;
+import org.apache.nifi.serialization.DateTimeUtils;
 import org.apache.nifi.serialization.SchemaRegistryRecordSetWriter;
 import org.apache.nifi.util.MockPropertyConfiguration;
+import org.apache.nifi.util.NoOpProcessor;
 import org.apache.nifi.util.PropertyMigrationResult;
+import org.apache.nifi.util.TestRunner;
+import org.apache.nifi.util.TestRunners;
 import org.junit.jupiter.api.Test;
 
 import java.util.Map;
@@ -33,9 +38,23 @@ import static 
org.apache.nifi.schema.access.SchemaAccessUtils.SCHEMA_REGISTRY;
 import static org.apache.nifi.schema.access.SchemaAccessUtils.SCHEMA_TEXT;
 import static org.apache.nifi.schema.access.SchemaAccessUtils.SCHEMA_VERSION;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 public class TestJsonRecordSetWriter {
 
+    @Test
+    void testFormattedStringRequiresTimestampFormat() throws 
InitializationException {
+        final JsonRecordSetWriter service = new JsonRecordSetWriter();
+        final TestRunner runner = 
TestRunners.newTestRunner(NoOpProcessor.class);
+        runner.addControllerService("writer", service);
+        runner.setProperty(service, 
JsonRecordSetWriter.TIMESTAMP_REPRESENTATION, 
TimestampRepresentation.FORMATTED_STRING.name());
+
+        assertThrows(IllegalStateException.class, () -> 
runner.enableControllerService(service));
+
+        runner.setProperty(service, DateTimeUtils.TIMESTAMP_FORMAT, 
"yyyy-MM-dd'T'HH:mm:ss.SSSX");
+        runner.enableControllerService(service);
+    }
+
     @Test
     void testMigrateProperties() {
         final JsonRecordSetWriter service = new JsonRecordSetWriter();
diff --git 
a/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestWriteJsonResult.java
 
b/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestWriteJsonResult.java
index 9248f24d9bb..572a9cd76c2 100644
--- 
a/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestWriteJsonResult.java
+++ 
b/nifi-extension-bundles/nifi-standard-services/nifi-record-serialization-services-bundle/nifi-record-serialization-services/src/test/java/org/apache/nifi/json/TestWriteJsonResult.java
@@ -271,6 +271,105 @@ class TestWriteJsonResult {
         assertEquals(expected, output);
     }
 
+    @Test
+    void testTimestampRepresentations() throws IOException {
+        final RecordSchema schema = new SimpleRecordSchema(List.of(new 
RecordField("timestamp", RecordFieldType.TIMESTAMP.getDataType())));
+        final Timestamp timestamp = new Timestamp(1623926285001L);
+        final Record record = new MapRecord(schema, Map.of("timestamp", 
timestamp));
+
+        assertEquals("[{\"timestamp\":\"formatted-001\"}]",
+                writeTimestampRecord(record, "'formatted-'SSS", 
TimestampRepresentation.FORMATTED_STRING, false));
+        assertEquals("[{\"timestamp\":1623926285001}]", 
writeTimestampRecord(record, null, TimestampRepresentation.EPOCH_MILLISECONDS, 
false));
+        assertEquals("[{\"timestamp\":1623926285.001}]", 
writeTimestampRecord(record, null, TimestampRepresentation.EPOCH_SECONDS, 
false));
+    }
+
+    @Test
+    void testEpochSecondsValues() throws IOException {
+        final RecordSchema schema = new SimpleRecordSchema(List.of(new 
RecordField("timestamp", RecordFieldType.TIMESTAMP.getDataType())));
+        final long[] epochMilliseconds = {1623926285000L, 1623926285001L, 
1623926285999L, 0L, -1L, 253402300799999L};
+        final String[] expectedValues = {"1623926285.000", "1623926285.001", 
"1623926285.999", "0.000", "-0.001", "253402300799.999"};
+
+        for (int i = 0; i < epochMilliseconds.length; i++) {
+            final Record record = new MapRecord(schema, Map.of("timestamp", 
new Timestamp(epochMilliseconds[i])));
+            assertEquals("[{\"timestamp\":" + expectedValues[i] + "}]", 
writeTimestampRecord(record, null, TimestampRepresentation.EPOCH_SECONDS, 
false));
+        }
+    }
+
+    @Test
+    void testEpochSecondsRawRecord() throws IOException {
+        final RecordSchema schema = new SimpleRecordSchema(List.of(new 
RecordField("timestamp", RecordFieldType.TIMESTAMP.getDataType())));
+        final Record record = new MapRecord(schema, Map.of("timestamp", new 
Timestamp(1623926285001L)));
+
+        assertEquals("[{\"timestamp\":1623926285.001}]", 
writeTimestampRecord(record, null, TimestampRepresentation.EPOCH_SECONDS, 
true));
+    }
+
+    @Test
+    void testEpochSecondsDoesNotChangeDateAndTime() throws IOException {
+        final Date date = Date.valueOf("1970-01-01");
+        final Time time = new Time(37293723L);
+        final RecordSchema schema = new SimpleRecordSchema(List.of(
+                new RecordField("timestamp", 
RecordFieldType.TIMESTAMP.getDataType()),
+                new RecordField("date", RecordFieldType.DATE.getDataType()),
+                new RecordField("time", RecordFieldType.TIME.getDataType())));
+        final Record record = new MapRecord(schema, Map.of("timestamp", new 
Timestamp(37293723L), "date", date, "time", time));
+
+        
assertEquals(String.format("[{\"timestamp\":37293.723,\"date\":%d,\"time\":37293723}]",
 date.getTime()),
+                writeTimestampRecord(record, null, 
TimestampRepresentation.EPOCH_SECONDS, false));
+    }
+
+    @Test
+    void testEpochSecondsNestedArrayAndChoice() throws IOException {
+        final DataType timestampType = RecordFieldType.TIMESTAMP.getDataType();
+        final RecordSchema nestedSchema = new SimpleRecordSchema(List.of(new 
RecordField("timestamp", timestampType)));
+        final Record nestedRecord = new MapRecord(nestedSchema, 
Map.of("timestamp", new Timestamp(1623926285001L)));
+        final List<RecordField> fields = List.of(
+                new RecordField("nested", 
RecordFieldType.RECORD.getRecordDataType(nestedSchema)),
+                new RecordField("timestamps", 
RecordFieldType.ARRAY.getArrayDataType(timestampType)),
+                new RecordField("choice", 
RecordFieldType.CHOICE.getChoiceDataType(timestampType, 
RecordFieldType.STRING.getDataType())));
+        final RecordSchema schema = new SimpleRecordSchema(fields);
+        final Record record = new MapRecord(schema, Map.of(
+                "nested", nestedRecord,
+                "timestamps", new Timestamp[]{new Timestamp(0L), new 
Timestamp(-1L)},
+                "choice", new Timestamp(1623926285999L)));
+
+        
assertEquals("[{\"nested\":{\"timestamp\":1623926285.001},\"timestamps\":[0.000,-0.001],\"choice\":1623926285.999}]",
+                writeTimestampRecord(record, null, 
TimestampRepresentation.EPOCH_SECONDS, false));
+    }
+
+    @Test
+    void testExplicitTimestampRepresentationDisablesSerializedFormReuse() 
throws IOException {
+        final RecordSchema schema = new SimpleRecordSchema(List.of(new 
RecordField("timestamp", RecordFieldType.TIMESTAMP.getDataType())));
+        final Record record = new MapRecord(schema, Map.of("timestamp", new 
Timestamp(1623926285001L)),
+                SerializedForm.of("{\"timestamp\":\"preserved\"}", 
"application/json"));
+
+        assertEquals("[{\"timestamp\":\"formatted-001\"}]", 
writeTimestampRecord(record, "'formatted-'SSS", 
TimestampRepresentation.FORMATTED_STRING, false));
+        assertEquals("[{\"timestamp\":1623926285001}]", 
writeTimestampRecord(record, null, TimestampRepresentation.EPOCH_MILLISECONDS, 
false));
+        assertEquals("[{\"timestamp\":1623926285.001}]", 
writeTimestampRecord(record, null, TimestampRepresentation.EPOCH_SECONDS, 
false));
+        assertEquals("[{\"timestamp\":1623926285.001}]", 
writeTimestampRecord(record, null, TimestampRepresentation.EPOCH_SECONDS, 
false, true));
+    }
+
+    private String writeTimestampRecord(final Record record, final String 
timestampFormat, final TimestampRepresentation timestampRepresentation,
+            final boolean rawRecord) throws IOException {
+        return writeTimestampRecord(record, timestampFormat, 
timestampRepresentation, rawRecord, false);
+    }
+
+    private String writeTimestampRecord(final Record record, final String 
timestampFormat, final TimestampRepresentation timestampRepresentation,
+            final boolean rawRecord, final boolean allowScientificNotation) 
throws IOException {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (final WriteJsonResult writer = new 
WriteJsonResult(Mockito.mock(ComponentLog.class), record.getSchema(), new 
SchemaNameAsAttribute(), baos, false,
+                NullSuppression.NEVER_SUPPRESS, OutputGrouping.OUTPUT_ARRAY, 
null, null, timestampFormat, "application/json", allowScientificNotation, true,
+                timestampRepresentation)) {
+            writer.beginRecordSet();
+            if (rawRecord) {
+                writer.writeRawRecord(record);
+            } else {
+                writer.writeRecord(record);
+            }
+            writer.finishRecordSet();
+        }
+        return baos.toString(StandardCharsets.UTF_8);
+    }
+
     @Test
     void testExtraFieldInWriteRecord() throws IOException {
         final List<RecordField> fields = new ArrayList<>();

Reply via email to