dbMango/Rms.Risk.Mango.Pivot.Core/ArrayBasedPivotDataJsonSerializer.cs
Alexander Shabarshov 2a7a24c9e7 Initial contribution
2025-11-03 14:43:26 +00:00

400 lines
14 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* dbMango
*
* Copyright 2025 Deutsche Bank AG
* SPDX-License-Identifier: Apache-2.0
*
* Licensed 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.Runtime.Serialization;
#if MSJSON
using System.Text.Json;
using System.Text.Json.Serialization;
#else
using Newtonsoft.Json;
#endif
namespace Rms.Risk.Mango.Pivot.Core;
#if MSJSON
public class ArrayBasedPivotDataJsonSerializer : JsonConverter<ArrayBasedPivotData>
{
public override void Write(
Utf8JsonWriter writer,
ArrayBasedPivotData value,
JsonSerializerOptions options)
{
if ( string.IsNullOrWhiteSpace( value.Id ) )
throw new SerializationException( "Id is not set for ArrayBasedPivotData instance" );
var headers = value.Headers.OrderBy(x => x.Value).Select(x => x.Key).ToList();
if ( headers.Count == 0 )
throw new SerializationException("Headers count is zero for ArrayBasedPivotData instance");
writer.WriteStartObject();
writer.WritePropertyName("_id");
writer.WriteStringValue(value.Id);
writer.WritePropertyName("ExpireAt");
writer.WriteStringValue(value.ExpireAt.ToString("O"));
writer.WritePropertyName("Headers");
writer.WriteStartArray();
foreach ( var header in headers )
writer.WriteStringValue( header );
writer.WriteEndArray();
writer.WritePropertyName( "Data" );
writer.WriteStartArray();
for ( var row = 0; row < value.Count; row ++ )
{
writer.WriteStartArray();
for ( var col = 0; col < headers.Count; col++ )
{
var data = value.Get( col, row );
if ( data == null )
{
writer.WriteNullValue();
continue;
}
var t = data.GetType();
if (t == typeof(double))
writer.WriteNumberValue((double)data);
else if (t == typeof(string))
writer.WriteStringValue((string)data);
else if (t == typeof(int))
writer.WriteNumberValue((int)data);
else if (t == typeof(long))
writer.WriteNumberValue((long)data);
else if (t == typeof(DateTime))
writer.WriteStringValue(((DateTime)data).ToString("O"));
else if (t == typeof(bool))
writer.WriteBooleanValue((bool)data);
else
writer.WriteNullValue();
}
writer.WriteEndArray();
}
writer.WriteEndArray();
writer.WriteEndObject();
}
private static void Expect( Utf8JsonReader reader, JsonTokenType expected )
{
if ( !reader.Read() )
throw new SerializationException();
if ( reader.TokenType != expected )
throw new SerializationException($"Expected {expected} but got {reader.TokenType}");
}
private static void ReadStartArray( Utf8JsonReader reader ) => Expect( reader, JsonTokenType.StartArray );
private static void ReadEndProperty( Utf8JsonReader reader ) => Expect( reader, JsonTokenType.EndObject );
private static void ReadProperty( Utf8JsonReader reader, string name )
{
Expect( reader, JsonTokenType.PropertyName );
var n = reader.GetString();
if ( n != name )
throw new SerializationException($"Expected \"{name}\" but got \"{n}\"");
}
public override ArrayBasedPivotData Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
ReadProperty(reader, "_id");
var id = reader.GetString();
ReadProperty(reader, "ExpireAt");
var expireAt = DateTime.Parse(reader.GetString());
ReadProperty(reader, "Headers");
ReadStartArray(reader);
var headers = new List<string>();
while ( reader.Read() )
{
var tokenType = reader.TokenType;
if ( tokenType == JsonTokenType.EndArray )
break;
headers.Add( reader.GetString() );
}
var data = new ArrayBasedPivotData( headers )
{
Id = id,
ExpireAt = expireAt
};
ReadProperty(reader, "Data");
while ( reader.Read() )
{
var tokenType = reader.TokenType;
if ( tokenType == JsonTokenType.EndArray )
break;
if ( tokenType != JsonTokenType.StartArray )
continue;
var val = new object[headers.Count];
var col = 0;
while ( reader.Read() )
{
tokenType = reader.TokenType;
if ( tokenType == JsonTokenType.StartArray )
continue;
if ( tokenType == JsonTokenType.EndArray )
break;
switch ( tokenType )
{
case JsonTokenType.Comment:
case JsonTokenType.None:
break;
// case JsonTokenType.Float:
// case JsonTokenType.Boolean:
// case JsonTokenType.Date:
// case JsonTokenType.Integer:
// case JsonTokenType.Bytes:
case JsonTokenType.String:
val[col] = reader.GetString();
break;
case JsonTokenType.Number:
var s = reader.GetString();
// since doubles are more than 90% of the data make it quick
if ( s.IndexOf(".", StringComparison.Ordinal) >= 0 && double.TryParse( s, out var d ) )
val[col] = d;
if ( int.TryParse( s, out var i ) )
val[col] = i;
if ( long.TryParse( s, out var l ) )
val[col] = l;
if ( double.TryParse( s, out d ) )
val[col] = d;
break;
case JsonTokenType.Null:
val[col] = null;
break;
// case JsonToken.StartObject:
// case JsonToken.StartArray:
// case JsonToken.StartConstructor:
// case JsonToken.PropertyName:
// case JsonToken.Raw:
// case JsonToken.Undefined:
// case JsonToken.EndObject:
// case JsonToken.EndConstructor:
default:
throw new SerializationException($"Unexpected token {tokenType}");
}
col += 1;
}
data.Add( val );
}
ReadEndProperty( reader );
return data;
}
public override bool CanConvert(Type objectType)
{
return typeof(ArrayBasedPivotData) == objectType;
}
}
#else
public class ArrayBasedPivotDataJsonSerializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object? val, JsonSerializer serializer)
{
if ( val is not ArrayBasedPivotData value )
throw new ArgumentNullException( nameof(value), "ArrayBasedPivotData instance is null" );
if ( string.IsNullOrWhiteSpace( value.Id ) )
throw new SerializationException( "Id is not set for ArrayBasedPivotData instance" );
var headers = value.Headers;
if ( headers.Count == 0 )
throw new SerializationException("Headers count is zero for ArrayBasedPivotData instance");
writer.WriteStartObject();
writer.WritePropertyName("_id");
writer.WriteValue(value.Id);
writer.WritePropertyName("ExpireAt");
writer.WriteValue(value.ExpireAt);
writer.WritePropertyName("Headers");
writer.WriteStartArray();
foreach ( var header in headers )
writer.WriteValue( header );
writer.WriteEndArray();
writer.WritePropertyName( "Data" );
writer.WriteStartArray();
for ( var row = 0; row < value.Count; row ++ )
{
writer.WriteStartArray();
for ( var col = 0; col < headers.Count; col++ )
{
var data = value.Get( col, row );
if ( data == null )
{
writer.WriteNull();
continue;
}
var t = data.GetType();
if (t == typeof(double))
writer.WriteValue((double)data);
else if (t == typeof(string))
writer.WriteValue((string)data);
else if (t == typeof(int))
writer.WriteValue((int)data);
else if (t == typeof(long))
writer.WriteValue((long)data);
else if (t == typeof(DateTime))
writer.WriteValue((DateTime)data);
else if (t == typeof(bool))
writer.WriteValue((bool)data);
else
writer.WriteNull();
}
writer.WriteEndArray();
}
writer.WriteEndArray();
writer.WriteEndObject();
}
private static void Expect( JsonReader reader, JsonToken expected )
{
if ( !reader.Read() )
throw new SerializationException($"Expected {expected} but got none");
if ( reader.TokenType != expected )
throw new SerializationException($"Expected {expected} but got {reader.TokenType}");
}
private static void ReadStartArray( JsonReader reader ) => Expect( reader, JsonToken.StartArray );
private static void ReadEndProperty( JsonReader reader ) => Expect( reader, JsonToken.EndObject );
private static void ReadProperty( JsonReader reader, string name )
{
Expect( reader, JsonToken.PropertyName );
if ( reader.ValueType != typeof(string))
throw new SerializationException($"Expected {name} but got \"{reader.Value}\" ({reader.ValueType})");
var n = reader.Value as string;
if ( n != name )
throw new SerializationException($"Expected \"{name}\" but got \"{n}\"");
}
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
ReadProperty(reader, "_id");
var id = reader.ReadAsString();
ReadProperty(reader, "ExpireAt");
var expireAt = reader.ReadAsDateTime();
ReadProperty(reader, "Headers");
ReadStartArray(reader);
var headers = new List<string>();
while ( reader.Read() )
{
var tokenType = reader.TokenType;
if ( tokenType == JsonToken.EndArray )
break;
headers.Add( (string)reader.Value! );
}
var data = new ArrayBasedPivotData( headers )
{
Id = id ?? "",
ExpireAt = expireAt ?? default(DateTime)
};
if (data.Id == "no_data")
return data;
ReadProperty(reader, "Data");
while ( reader.Read() )
{
var tokenType = reader.TokenType;
if ( tokenType == JsonToken.EndArray )
break;
if ( tokenType != JsonToken.StartArray )
continue;
var val = new object[headers.Count];
var col = 0;
while ( reader.Read() )
{
tokenType = reader.TokenType;
if ( tokenType == JsonToken.StartArray )
continue;
if ( tokenType == JsonToken.EndArray )
break;
switch ( tokenType )
{
case JsonToken.Comment:
case JsonToken.None:
break;
case JsonToken.Float:
case JsonToken.String:
case JsonToken.Boolean:
case JsonToken.Date:
case JsonToken.Null:
case JsonToken.Integer:
case JsonToken.Bytes:
val[col] = reader.Value!;
break;
// case JsonToken.StartObject:
// case JsonToken.StartArray:
// case JsonToken.StartConstructor:
// case JsonToken.PropertyName:
// case JsonToken.Raw:
// case JsonToken.Undefined:
// case JsonToken.EndObject:
// case JsonToken.EndConstructor:
default:
throw new SerializationException($"Unexpected token {tokenType}");
}
col += 1;
}
data.Add( val );
}
ReadEndProperty( reader );
return data;
}
public override bool CanRead => true;
public override bool CanConvert(Type objectType) => typeof(ArrayBasedPivotData) == objectType;
}
#endif