blob: ff6af3d32e46dcfd1126138a4ea5046a1ed75d07 [file]
// 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.
using System;
#if NET8_0_OR_GREATER
using System.Buffers;
#else
using System.IO;
#endif
using System.Text;
using System.Text.Json;
using Apache.Arrow.Scalars.Variant;
namespace Apache.Arrow.Operations.VariantJson
{
/// <summary>
/// Writes variant binary data directly to JSON without creating intermediate
/// <see cref="VariantValue"/> objects.
/// </summary>
public static class VariantJsonWriter
{
/// <summary>
/// Converts variant binary data to a JSON string.
/// </summary>
/// <param name="metadata">The variant metadata bytes.</param>
/// <param name="value">The variant value bytes.</param>
/// <param name="indented">Whether to produce indented (pretty-printed) JSON.</param>
/// <returns>The JSON string representation.</returns>
public static string ToJson(byte[] metadata, byte[] value, bool indented = false)
{
JsonWriterOptions writerOptions = new JsonWriterOptions { Indented = indented };
#if NET8_0_OR_GREATER
ArrayBufferWriter<byte> buffer = new ArrayBufferWriter<byte>();
using (Utf8JsonWriter writer = new Utf8JsonWriter(buffer, writerOptions))
{
VariantReader reader = new VariantReader(metadata, value);
WriteReader(writer, reader);
}
return Encoding.UTF8.GetString(buffer.WrittenSpan);
#else
using (MemoryStream ms = new MemoryStream())
{
using (Utf8JsonWriter writer = new Utf8JsonWriter(ms, writerOptions))
{
VariantReader reader = new VariantReader(metadata, value);
WriteReader(writer, reader);
}
return Encoding.UTF8.GetString(ms.GetBuffer(), 0, checked((int)ms.Position));
}
#endif
}
/// <summary>
/// Converts a <see cref="VariantValue"/> to a JSON string.
/// </summary>
/// <param name="value">The variant value to serialize.</param>
/// <param name="indented">Whether to produce indented (pretty-printed) JSON.</param>
/// <returns>The JSON string representation.</returns>
public static string ToJson(VariantValue value, bool indented = false)
{
JsonWriterOptions writerOptions = new JsonWriterOptions { Indented = indented };
#if NET8_0_OR_GREATER
ArrayBufferWriter<byte> buffer = new ArrayBufferWriter<byte>();
using (Utf8JsonWriter writer = new Utf8JsonWriter(buffer, writerOptions))
{
VariantJsonConverter.WriteValue(writer, value);
}
return Encoding.UTF8.GetString(buffer.WrittenSpan);
#else
using (MemoryStream ms = new MemoryStream())
{
using (Utf8JsonWriter writer = new Utf8JsonWriter(ms, writerOptions))
{
VariantJsonConverter.WriteValue(writer, value);
}
return Encoding.UTF8.GetString(ms.GetBuffer(), 0, checked((int)ms.Position));
}
#endif
}
/// <summary>
/// Writes variant binary data to a <see cref="Utf8JsonWriter"/>.
/// </summary>
/// <param name="writer">The JSON writer to write to.</param>
/// <param name="metadata">The variant metadata bytes.</param>
/// <param name="value">The variant value bytes.</param>
public static void WriteTo(Utf8JsonWriter writer, byte[] metadata, byte[] value)
{
VariantReader reader = new VariantReader(metadata, value);
WriteReader(writer, reader);
}
private static void WriteReader(Utf8JsonWriter writer, VariantReader reader)
{
switch (reader.BasicType)
{
case VariantBasicType.ShortString:
writer.WriteStringValue(reader.GetStringBytes());
return;
case VariantBasicType.Object:
VariantObjectReader obj = new VariantObjectReader(reader.Metadata, reader.Value);
writer.WriteStartObject();
for (int i = 0; i < obj.FieldCount; i++)
{
writer.WritePropertyName(obj.GetFieldNameBytes(i));
VariantReader fieldValue = obj.GetFieldValue(i);
WriteReader(writer, fieldValue);
}
writer.WriteEndObject();
return;
case VariantBasicType.Array:
VariantArrayReader arr = new VariantArrayReader(reader.Metadata, reader.Value);
writer.WriteStartArray();
for (int i = 0; i < arr.ElementCount; i++)
{
VariantReader elem = arr.GetElement(i);
WriteReader(writer, elem);
}
writer.WriteEndArray();
return;
case VariantBasicType.Primitive:
WritePrimitive(writer, reader);
return;
default:
throw new NotSupportedException($"Unsupported basic type {reader.BasicType}.");
}
}
private static void WritePrimitive(Utf8JsonWriter writer, VariantReader reader)
{
switch (reader.PrimitiveType)
{
case VariantPrimitiveType.NullType:
writer.WriteNullValue();
break;
case VariantPrimitiveType.BooleanTrue:
writer.WriteBooleanValue(true);
break;
case VariantPrimitiveType.BooleanFalse:
writer.WriteBooleanValue(false);
break;
case VariantPrimitiveType.Int8:
writer.WriteNumberValue(reader.GetInt8());
break;
case VariantPrimitiveType.Int16:
writer.WriteNumberValue(reader.GetInt16());
break;
case VariantPrimitiveType.Int32:
writer.WriteNumberValue(reader.GetInt32());
break;
case VariantPrimitiveType.Int64:
writer.WriteNumberValue(reader.GetInt64());
break;
case VariantPrimitiveType.Float:
float f = reader.GetFloat();
if (float.IsNaN(f) || float.IsInfinity(f))
{
throw new InvalidOperationException(
$"Cannot serialize {f} to JSON. NaN and Infinity are not valid JSON numbers.");
}
writer.WriteNumberValue(f);
break;
case VariantPrimitiveType.Double:
double d = reader.GetDouble();
if (double.IsNaN(d) || double.IsInfinity(d))
{
throw new InvalidOperationException(
$"Cannot serialize {d} to JSON. NaN and Infinity are not valid JSON numbers.");
}
writer.WriteNumberValue(d);
break;
case VariantPrimitiveType.Decimal4:
writer.WriteNumberValue(reader.GetDecimal4());
break;
case VariantPrimitiveType.Decimal8:
writer.WriteNumberValue(reader.GetDecimal8());
break;
case VariantPrimitiveType.Decimal16:
if (reader.TryGetDecimal16(out decimal dec))
writer.WriteNumberValue(dec);
else
writer.WriteRawValue(reader.GetSqlDecimal().ToString());
break;
case VariantPrimitiveType.Date:
DateTime date = reader.GetDate();
#if NET8_0_OR_GREATER
Span<char> buf = stackalloc char[10]; // "yyyy-MM-dd"
date.TryFormat(buf, out int written, "yyyy-MM-dd");
writer.WriteStringValue(buf.Slice(0, written));
#else
writer.WriteStringValue(date.ToString("yyyy-MM-dd"));
#endif
break;
case VariantPrimitiveType.Timestamp:
DateTimeOffset ts = reader.GetTimestamp();
#if NET8_0_OR_GREATER
buf = stackalloc char[64];
ts.TryFormat(buf, out written, "O");
writer.WriteStringValue(buf.Slice(0, written));
#else
writer.WriteStringValue(ts.ToString("O"));
#endif
break;
case VariantPrimitiveType.TimestampNtz:
DateTime ntz = reader.GetTimestampNtz();
#if NET8_0_OR_GREATER
buf = stackalloc char[64];
ntz.TryFormat(buf, out written, "O");
writer.WriteStringValue(buf.Slice(0, written));
#else
writer.WriteStringValue(ntz.ToString("O"));
#endif
break;
case VariantPrimitiveType.TimeNtz:
writer.WriteNumberValue(reader.GetTimeNtzMicros());
break;
case VariantPrimitiveType.TimestampTzNanos:
writer.WriteNumberValue(reader.GetTimestampTzNanos());
break;
case VariantPrimitiveType.TimestampNtzNanos:
writer.WriteNumberValue(reader.GetTimestampNtzNanos());
break;
case VariantPrimitiveType.Binary:
writer.WriteBase64StringValue(reader.GetBinary());
break;
case VariantPrimitiveType.String:
writer.WriteStringValue(reader.GetStringBytes());
break;
case VariantPrimitiveType.Uuid:
#if NET8_0_OR_GREATER
Guid uuid = reader.GetUuid();
buf = stackalloc char[36]; // "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
uuid.TryFormat(buf, out written, "D");
writer.WriteStringValue(buf.Slice(0, written));
#else
writer.WriteStringValue(reader.GetUuid().ToString("D"));
#endif
break;
default:
throw new NotSupportedException($"Cannot serialize variant type {reader.PrimitiveType} to JSON.");
}
}
}
}