Browse Source

Different string representations.

pull/908/head
Sebastian 3 years ago
parent
commit
7e033cbadd
  1. 1
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDefaultConventions.cs
  2. 9
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonInstantSerializer.cs
  3. 12
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs
  4. 116
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonSerializer.cs
  5. 5
      backend/src/Squidex/Config/Domain/StoreServices.cs
  6. 5
      backend/src/Squidex/appsettings.json
  7. 90
      backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonJsonSerializerTests.cs
  8. 73
      backend/tests/Squidex.Infrastructure.Tests/MongoDb/InstantSerializerTests.cs

1
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDefaultConventions.cs

@ -18,7 +18,6 @@ namespace Squidex.Infrastructure.MongoDb
{
try
{
if (isRegistered)
{
return;

9
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonInstantSerializer.cs

@ -34,9 +34,14 @@ namespace Squidex.Infrastructure.MongoDb
public BsonType Representation { get; }
public BsonInstantSerializer(BsonType representation = BsonType.DateTime)
public BsonInstantSerializer()
: this(BsonType.DateTime)
{
if (representation != BsonType.DateTime && representation != BsonType.Int64 && representation != BsonType.String)
}
public BsonInstantSerializer(BsonType representation)
{
if (representation is not BsonType.DateTime and not BsonType.Int64 and not BsonType.String)
{
throw new ArgumentException("Unsupported representation.", nameof(representation));
}

12
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System.Reflection;
using System.Reflection.Metadata;
using System.Text.Json;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
@ -17,9 +18,11 @@ namespace Squidex.Infrastructure.MongoDb
{
private static bool isRegistered;
public static JsonSerializerOptions Options { get; set; } = new JsonSerializerOptions(JsonSerializerDefaults.Web);
public static JsonSerializerOptions Options { get; private set; } = new JsonSerializerOptions(JsonSerializerDefaults.Web);
public static void Register(JsonSerializerOptions? options = null)
public static BsonType Representation { get; private set; } = BsonType.Document;
public static void Register(JsonSerializerOptions? options = null, BsonType? representation = null)
{
try
{
@ -28,6 +31,11 @@ namespace Squidex.Infrastructure.MongoDb
Options = options;
}
if (representation != null)
{
Representation = representation.Value;
}
if (isRegistered)
{
return;

116
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonSerializer.cs

@ -14,28 +14,63 @@ using Squidex.Infrastructure.ObjectPool;
namespace Squidex.Infrastructure.MongoDb
{
public sealed class BsonJsonSerializer<T> : ClassSerializerBase<T?> where T : class
public sealed class BsonJsonSerializer<T> : ClassSerializerBase<T?>, IRepresentationConfigurable<BsonJsonSerializer<T>> where T : class
{
public override T? Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
public BsonType Representation { get; }
public BsonType ActualRepresentation
{
var bsonReader = context.Reader;
get => Representation == BsonType.Undefined ? BsonJsonConvention.Representation : Representation;
}
if (bsonReader.GetCurrentBsonType() == BsonType.Null)
public JsonSerializerOptions Options
{
get => BsonJsonConvention.Options;
}
public BsonJsonSerializer()
: this(BsonType.Undefined)
{
}
public BsonJsonSerializer(BsonType representation)
{
if (representation is not BsonType.Undefined and not BsonType.String and not BsonType.Binary)
{
bsonReader.ReadNull();
return null;
throw new ArgumentException("Unsupported representation.", nameof(representation));
}
using var stream = DefaultPools.MemoryStream.GetStream();
Representation = representation;
}
using (var writer = new Utf8JsonWriter(stream))
public override T? Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var reader = context.Reader;
switch (reader.GetCurrentBsonType())
{
FromBson(bsonReader, writer);
}
case BsonType.Null:
reader.ReadNull();
return null;
case BsonType.String:
var valueString = reader.ReadString();
return JsonSerializer.Deserialize<T>(valueString, Options);
case BsonType.Binary:
var valueBinary = reader.ReadBytes();
return JsonSerializer.Deserialize<T>(valueBinary, Options);
default:
using (var stream = DefaultPools.MemoryStream.GetStream())
{
using (var writer = new Utf8JsonWriter(stream))
{
FromBson(reader, writer);
}
stream.Position = 0;
stream.Position = 0;
return JsonSerializer.Deserialize<T>(stream, BsonJsonConvention.Options);
return JsonSerializer.Deserialize<T>(stream, Options);
}
}
}
private static void FromBson(IBsonReader reader, Utf8JsonWriter writer)
@ -43,28 +78,34 @@ namespace Squidex.Infrastructure.MongoDb
void ReadDocument()
{
reader.ReadStartDocument();
writer.WriteStartObject();
while (reader.ReadBsonType() != BsonType.EndOfDocument)
{
Read();
writer.WriteStartObject();
while (reader.ReadBsonType() != BsonType.EndOfDocument)
{
Read();
}
writer.WriteEndObject();
}
writer.WriteEndObject();
reader.ReadEndDocument();
}
void ReadArray()
{
reader.ReadStartArray();
writer.WriteStartArray();
while (reader.ReadBsonType() != BsonType.EndOfDocument)
{
Read();
writer.WriteStartArray();
while (reader.ReadBsonType() != BsonType.EndOfDocument)
{
Read();
}
writer.WriteEndArray();
}
writer.WriteEndArray();
reader.ReadEndArray();
}
@ -135,11 +176,25 @@ namespace Squidex.Infrastructure.MongoDb
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, T? value)
{
var bsonWriter = context.Writer;
var writer = context.Writer;
using (var jsonDocument = JsonSerializer.SerializeToDocument(value, BsonJsonConvention.Options))
switch (ActualRepresentation)
{
WriteElement(bsonWriter, jsonDocument.RootElement);
case BsonType.String:
var jsonString = JsonSerializer.Serialize(value, args.NominalType, Options);
writer.WriteString(jsonString);
break;
case BsonType.Binary:
var jsonBytes = JsonSerializer.SerializeToUtf8Bytes(value, args.NominalType, Options);
writer.WriteBytes(jsonBytes);
break;
default:
using (var jsonDocument = JsonSerializer.SerializeToDocument(value, args.NominalType, Options))
{
WriteElement(writer, jsonDocument.RootElement);
}
break;
}
}
@ -178,6 +233,7 @@ namespace Squidex.Infrastructure.MongoDb
foreach (var property in element.EnumerateObject())
{
writer.WriteName(property.Name.EscapeJson());
WriteElement(writer, property.Value);
}
@ -188,5 +244,15 @@ namespace Squidex.Infrastructure.MongoDb
break;
}
}
public BsonJsonSerializer<T> WithRepresentation(BsonType representation)
{
return Representation == representation ? this : new BsonJsonSerializer<T>(representation);
}
IBsonSerializer IRepresentationConfigurable.WithRepresentation(BsonType representation)
{
return WithRepresentation(representation);
}
}
}

5
backend/src/Squidex/Config/Domain/StoreServices.cs

@ -9,6 +9,7 @@ using System.Text.Json;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Distributed;
using Migrations.Migrations.MongoDb;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Core.Extensions.DiagnosticSources;
using Squidex.Assets;
@ -176,7 +177,9 @@ namespace Squidex.Config.Domain
services.AddInitializer<JsonSerializerOptions>("Serializer (BSON)", jsonSerializerOptions =>
{
BsonJsonConvention.Options = jsonSerializerOptions;
var representation = config.GetValue<BsonType>("store:mongoDB:valueRepresentation");
BsonJsonConvention.Register(jsonSerializerOptions, representation);
}, int.MinValue);
}
});

5
backend/src/Squidex/appsettings.json

@ -485,6 +485,11 @@
// The database for all your other read collections.
"database": "Squidex",
// Defines how key-value-store values are represented in MongoDB (e.g. app, rule, schema).
//
// SUPPORTED: Undefined (Objects), String, Binary (from slow to fast).
"valueRepresentation": "Undefined",
"atlas": {
// The organization id.
"groupId": "",

90
backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonJsonSerializerTests.cs

@ -6,8 +6,9 @@
// ==========================================================================
using FluentAssertions;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Infrastructure.TestHelpers;
using Xunit;
namespace Squidex.Infrastructure.MongoDb
@ -20,6 +21,20 @@ namespace Squidex.Infrastructure.MongoDb
public T Value { get; set; }
}
public class TestWrapperString<T>
{
[BsonJson]
[BsonRepresentation(BsonType.String)]
public T Value { get; set; }
}
public class TestWrapperBinary<T>
{
[BsonJson]
[BsonRepresentation(BsonType.Binary)]
public T Value { get; set; }
}
public class TestObject
{
public bool Bool { get; set; }
@ -97,69 +112,90 @@ namespace Squidex.Infrastructure.MongoDb
}
[Fact]
public void Should_serialize_with_reader_and_writer()
public void Should_serialize_and_deserialize()
{
var source = TestObject.CreateWithValues();
var source = new TestWrapper<TestObject>
{
Value = TestObject.CreateWithValues()
};
var deserialized = SerializeAndDeserialize(source);
var deserialized = source.SerializeAndDeserializeBson();
deserialized.Should().BeEquivalentTo(source);
}
[Fact]
public void Should_deserialize_property_with_dollar()
public void Should_serialize_and_deserialize_as_string()
{
var source = new Dictionary<string, int>
var source = new TestWrapperString<TestObject>
{
["$key"] = 12
Value = TestObject.CreateWithValues()
};
var deserialized = SerializeAndDeserialize(source);
var deserialized = source.SerializeAndDeserializeBson();
deserialized.Should().BeEquivalentTo(source);
}
[Fact]
public void Should_deserialize_property_with_dot()
public void Should_serialize_and_deserialize_as_binary()
{
var source = new Dictionary<string, int>
var source = new TestWrapperBinary<TestObject>
{
["type.of.value"] = 12
Value = TestObject.CreateWithValues()
};
var deserialized = SerializeAndDeserialize(source);
var deserialized = source.SerializeAndDeserializeBson();
deserialized.Should().BeEquivalentTo(source);
}
[Fact]
public void Should_deserialize_property_as_empty_string()
public void Should_serialize_and_deserialize_property_with_dollar()
{
var source = new Dictionary<string, int>
var source = new TestWrapper<Dictionary<string, int>>
{
[string.Empty] = 12
Value = new Dictionary<string, int>
{
["$key"] = 12
}
};
var deserialized = SerializeAndDeserialize(source);
var deserialized = source.SerializeAndDeserializeBson();
deserialized.Should().BeEquivalentTo(source);
}
private static T SerializeAndDeserialize<T>(T value)
[Fact]
public void Should_serialize_and_deserialize_property_with_dot()
{
var stream = new MemoryStream();
using (var writer = new BsonBinaryWriter(stream))
var source = new TestWrapper<Dictionary<string, int>>
{
BsonSerializer.Serialize(writer, new TestWrapper<T> { Value = value });
}
Value = new Dictionary<string, int>
{
["type.of.value"] = 12
}
};
stream.Position = 0;
var deserialized = source.SerializeAndDeserializeBson();
using (var reader = new BsonBinaryReader(stream))
deserialized.Should().BeEquivalentTo(source);
}
[Fact]
public void Should_serialize_and_deserialize_property_as_empty_string()
{
var source = new TestWrapper<Dictionary<string, int>>
{
return BsonSerializer.Deserialize<TestWrapper<T>>(reader)!.Value;
}
Value = new Dictionary<string, int>
{
[string.Empty] = 12
}
};
var deserialized = source.SerializeAndDeserializeBson();
deserialized.Should().BeEquivalentTo(source);
}
}
}

73
backend/tests/Squidex.Infrastructure.Tests/MongoDb/InstantSerializerTests.cs

@ -5,9 +5,8 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using NodaTime;
using Squidex.Infrastructure.TestHelpers;
using Xunit;
namespace Squidex.Infrastructure.MongoDb
@ -20,74 +19,60 @@ namespace Squidex.Infrastructure.MongoDb
}
[Fact]
public void Should_serialize_as_default()
public void Should_serialize_and_deserialize()
{
var source = new Entities.DefaultEntity<Instant> { Value = GetTime() };
var source = new Entities.DefaultEntity<Instant>
{
Value = GetTime()
};
var result1 = SerializeAndDeserializeBson(source);
var deserialized = source.SerializeAndDeserializeBson();
Assert.Equal(source.Value, result1.Value);
Assert.Equal(source.Value, deserialized.Value);
}
[Fact]
public void Should_serialize_as_string()
public void Should_serialize_and_deserialize_as_string()
{
var source = new Entities.StringEntity<Instant> { Value = GetTime() };
var source = new Entities.StringEntity<Instant>
{
Value = GetTime()
};
var result1 = SerializeAndDeserializeBson(source);
var deserialized = source.SerializeAndDeserializeBson();
Assert.Equal(source.Value, result1.Value);
Assert.Equal(source.Value, deserialized.Value);
}
[Fact]
public void Should_serialize_as_int64()
public void Should_serialize_and_deserialize_as_int64()
{
var source = new Entities.Int64Entity<Instant> { Value = GetTime() };
var source = new Entities.Int64Entity<Instant>
{
Value = GetTime()
};
var result1 = SerializeAndDeserializeBson(source);
var deserialized = source.SerializeAndDeserializeBson();
Assert.Equal(source.Value, result1.Value);
Assert.Equal(source.Value, deserialized.Value);
}
[Fact]
public void Should_serialize_as_datetime()
public void Should_serialize_and_deserialize_as_datetime()
{
var source = new Entities.DateTimeEntity<Instant> { Value = GetTime() };
var source = new Entities.DateTimeEntity<Instant>
{
Value = GetTime()
};
var result1 = SerializeAndDeserializeBson(source);
var deserialized = source.SerializeAndDeserializeBson();
Assert.Equal(source.Value, result1.Value);
Assert.Equal(source.Value, deserialized.Value);
}
private static Instant GetTime()
{
return SystemClock.Instance.GetCurrentInstant().WithoutNs();
}
private static T SerializeAndDeserializeBson<T>(T source)
{
return SerializeAndDeserializeBson<T, T>(source);
}
private static TOut SerializeAndDeserializeBson<TIn, TOut>(TIn source)
{
var stream = new MemoryStream();
using (var writer = new BsonBinaryWriter(stream))
{
BsonSerializer.Serialize(writer, source);
writer.Flush();
}
stream.Position = 0;
using (var reader = new BsonBinaryReader(stream))
{
var target = BsonSerializer.Deserialize<TOut>(reader);
return target;
}
}
}
}

Loading…
Cancel
Save