blob: 0c51c5dd955d5509979987df32bc51b09b48053e [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;
using System.Text;
using System.Text.Json;
using Apache.Arrow.Scalars.Variant;
namespace Apache.Arrow.Operations.VariantJson
{
/// <summary>
/// Parses a JSON string or UTF-8 bytes directly into variant binary format
/// (metadata + value byte arrays) without creating intermediate <see cref="VariantValue"/> objects.
/// </summary>
public static class VariantJsonReader
{
/// <summary>
/// Parses a JSON string into variant binary format.
/// </summary>
/// <param name="json">The JSON string to parse.</param>
/// <returns>A tuple of (metadata bytes, value bytes).</returns>
public static (byte[] Metadata, byte[] Value) Parse(string json)
{
byte[] utf8 = Encoding.UTF8.GetBytes(json);
return Parse(new ReadOnlySpan<byte>(utf8));
}
/// <summary>
/// Parses UTF-8 encoded JSON bytes into variant binary format.
/// </summary>
/// <param name="utf8Json">The UTF-8 encoded JSON bytes.</param>
/// <returns>A tuple of (metadata bytes, value bytes).</returns>
public static (byte[] Metadata, byte[] Value) Parse(ReadOnlySpan<byte> utf8Json)
{
// Pass 1: collect all field names into the metadata dictionary.
VariantMetadataBuilder metadataBuilder = new VariantMetadataBuilder();
Utf8JsonReader collector = new Utf8JsonReader(utf8Json);
collector.Read();
CollectFieldNames(ref collector, metadataBuilder);
byte[] metadata = metadataBuilder.Build(out int[] idRemap);
// Pass 2: stream values into a VariantValueWriter using the sorted field IDs.
Utf8JsonReader emitter = new Utf8JsonReader(utf8Json);
emitter.Read();
using VariantValueWriter writer = new VariantValueWriter(metadataBuilder, idRemap);
WriteValue(ref emitter, writer);
return (metadata, writer.ToArray());
}
private static void CollectFieldNames(ref Utf8JsonReader reader, VariantMetadataBuilder builder)
{
switch (reader.TokenType)
{
case JsonTokenType.StartObject:
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
return;
builder.Add(reader.GetString());
reader.Read();
CollectFieldNames(ref reader, builder);
}
throw new JsonException("Unterminated JSON object.");
case JsonTokenType.StartArray:
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
return;
CollectFieldNames(ref reader, builder);
}
throw new JsonException("Unterminated JSON array.");
default:
return;
}
}
private static void WriteValue(ref Utf8JsonReader reader, VariantValueWriter writer)
{
switch (reader.TokenType)
{
case JsonTokenType.Null:
writer.WriteNull();
return;
case JsonTokenType.True:
writer.WriteBoolean(true);
return;
case JsonTokenType.False:
writer.WriteBoolean(false);
return;
case JsonTokenType.Number:
if (reader.TryGetInt64(out long longValue))
{
writer.WriteIntegerCompact(longValue);
}
else
{
writer.WriteDouble(reader.GetDouble());
}
return;
case JsonTokenType.String:
writer.WriteString(reader.GetString());
return;
case JsonTokenType.StartObject:
writer.BeginObject();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
break;
writer.WriteFieldName(reader.GetString());
reader.Read();
WriteValue(ref reader, writer);
}
writer.EndObject();
return;
case JsonTokenType.StartArray:
writer.BeginArray();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
break;
WriteValue(ref reader, writer);
}
writer.EndArray();
return;
default:
throw new JsonException($"Unexpected JSON token type {reader.TokenType}.");
}
}
}
}