Skip to content

Commit

Permalink
Support customized reading and writing of JSON values
Browse files Browse the repository at this point in the history
Part of #30730

- Type mappings now have a `JsonValueReaderWriter` that handles reading and writing JSON for the type.
- This can be overridden by a `JsonValueReaderWriter` on a property, for example, to allow GeoJson to be written instead of WKT.
- These are typically singleton instances, and can be optimized out for well-known cases.
- If the type mapping has a converter, then the converter must be applied to the value before being written to JSON, and the converter must be applied after reading the JSON value.
- If the property itself has a `JsonValueReaderWriter`, then the unconverted property value is passed to this reader/writer.
  • Loading branch information
ajcvickers committed Jun 2, 2023
1 parent 729f882 commit 9ab8043
Show file tree
Hide file tree
Showing 110 changed files with 4,085 additions and 211 deletions.
8 changes: 6 additions & 2 deletions src/EFCore.Cosmos/Storage/Internal/CosmosTypeMapping.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;

/// <summary>
Expand All @@ -20,13 +22,15 @@ public class CosmosTypeMapping : CoreTypeMapping
public CosmosTypeMapping(
Type clrType,
ValueComparer? comparer = null,
ValueComparer? keyComparer = null)
ValueComparer? keyComparer = null,
JsonValueReaderWriter? jsonValueReaderWriter = null)
: base(
new CoreTypeMappingParameters(
clrType,
converter: null,
comparer,
keyComparer))
keyComparer,
jsonValueReaderWriter: jsonValueReaderWriter))
{
}

Expand Down
27 changes: 20 additions & 7 deletions src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
: base(dependencies)
{
_clrTypeMappings
= new Dictionary<Type, CosmosTypeMapping> { { typeof(JObject), new CosmosTypeMapping(typeof(JObject)) } };
= new Dictionary<Type, CosmosTypeMapping>
{
{
typeof(JObject), new CosmosTypeMapping(
typeof(JObject), jsonValueReaderWriter: dependencies.JsonValueReaderWriterSource.FindReaderWriter(typeof(JObject)))
}
};
}

/// <summary>
Expand All @@ -47,21 +53,22 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
?? base.FindMapping(mappingInfo));
}

private static CoreTypeMapping? FindPrimitiveMapping(in TypeMappingInfo mappingInfo)
private CoreTypeMapping? FindPrimitiveMapping(in TypeMappingInfo mappingInfo)
{
var clrType = mappingInfo.ClrType!;
if ((clrType.IsValueType
&& clrType != typeof(Guid)
&& !clrType.IsEnum)
|| clrType == typeof(string))
{
return new CosmosTypeMapping(clrType);
return new CosmosTypeMapping(
clrType, jsonValueReaderWriter: Dependencies.JsonValueReaderWriterSource.FindReaderWriter(clrType));
}

return null;
}

private static CoreTypeMapping? FindCollectionMapping(in TypeMappingInfo mappingInfo)
private CoreTypeMapping? FindCollectionMapping(in TypeMappingInfo mappingInfo)
{
var clrType = mappingInfo.ClrType!;
var elementType = clrType.TryGetSequenceType();
Expand All @@ -70,14 +77,17 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
return null;
}

var jsonValueReaderWriter = Dependencies.JsonValueReaderWriterSource.FindReaderWriter(clrType);

if (clrType.IsArray)
{
var elementMappingInfo = new TypeMappingInfo(elementType);
var elementMapping = FindPrimitiveMapping(elementMappingInfo)
?? FindCollectionMapping(elementMappingInfo);
return elementMapping == null
? null
: new CosmosTypeMapping(clrType, CreateArrayComparer(elementMapping, elementType));
: new CosmosTypeMapping(
clrType, CreateArrayComparer(elementMapping, elementType), jsonValueReaderWriter: jsonValueReaderWriter);
}

if (clrType.IsGenericType
Expand All @@ -93,7 +103,8 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
?? FindCollectionMapping(elementMappingInfo);
return elementMapping == null
? null
: new CosmosTypeMapping(clrType, CreateListComparer(elementMapping, elementType, clrType));
: new CosmosTypeMapping(
clrType, CreateListComparer(elementMapping, elementType, clrType), jsonValueReaderWriter: jsonValueReaderWriter);
}

if (genericTypeDefinition == typeof(Dictionary<,>)
Expand All @@ -112,7 +123,9 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
?? FindCollectionMapping(elementMappingInfo);
return elementMapping == null
? null
: new CosmosTypeMapping(clrType, CreateStringDictionaryComparer(elementMapping, elementType, clrType));
: new CosmosTypeMapping(
clrType, CreateStringDictionaryComparer(elementMapping, elementType, clrType),
jsonValueReaderWriter: jsonValueReaderWriter);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,31 @@ private void Create(
.Append("()");
}

var jsonValueReaderWriterType = (Type?)property[CoreAnnotationNames.JsonValueReaderWriterType];
if (jsonValueReaderWriterType != null)
{
AddNamespace(jsonValueReaderWriterType, parameters.Namespaces);

var instanceProperty = jsonValueReaderWriterType.GetAnyProperty("Instance");
if (instanceProperty != null
&& instanceProperty.IsStatic()
&& instanceProperty.GetMethod?.IsPublic == true
&& jsonValueReaderWriterType.IsAssignableFrom(instanceProperty.PropertyType))
{
mainBuilder.AppendLine(",")
.Append("jsonValueReaderWriter: ")
.Append(_code.Reference(jsonValueReaderWriterType))
.Append(".Instance");
}
else
{
mainBuilder.AppendLine(",")
.Append("jsonValueReaderWriter: new ")
.Append(_code.Reference(jsonValueReaderWriterType))
.Append("()");
}
}

var sentinel = property.Sentinel;
if (sentinel != null)
{
Expand Down Expand Up @@ -900,8 +925,9 @@ private void Create(
}

return i == ForeignKey.LongestFkChainAllowedLength
? throw new InvalidOperationException(CoreStrings.RelationshipCycle(
property.DeclaringEntityType.DisplayName(), property.Name, "ValueConverterType"))
? throw new InvalidOperationException(
CoreStrings.RelationshipCycle(
property.DeclaringEntityType.DisplayName(), property.Name, "ValueConverterType"))
: null;
}

Expand Down Expand Up @@ -1474,18 +1500,13 @@ private static void CreateAnnotations<TAnnotatable>(
{
process(
annotatable,
parameters with
{
Annotations = annotatable.GetAnnotations().ToDictionary(a => a.Name, a => a.Value),
IsRuntime = false
});
parameters with { Annotations = annotatable.GetAnnotations().ToDictionary(a => a.Name, a => a.Value), IsRuntime = false });

process(
annotatable,
parameters with
{
Annotations = annotatable.GetRuntimeAnnotations().ToDictionary(a => a.Name, a => a.Value),
IsRuntime = true
Annotations = annotatable.GetRuntimeAnnotations().ToDictionary(a => a.Name, a => a.Value), IsRuntime = true
});
}

Expand Down
8 changes: 6 additions & 2 deletions src/EFCore.InMemory/Storage/Internal/InMemoryTypeMapping.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;

/// <summary>
Expand All @@ -20,13 +22,15 @@ public class InMemoryTypeMapping : CoreTypeMapping
public InMemoryTypeMapping(
Type clrType,
ValueComparer? comparer = null,
ValueComparer? keyComparer = null)
ValueComparer? keyComparer = null,
JsonValueReaderWriter? jsonValueReaderWriter = null)
: base(
new CoreTypeMappingParameters(
clrType,
converter: null,
comparer,
keyComparer))
keyComparer,
jsonValueReaderWriter: jsonValueReaderWriter))
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ public InMemoryTypeMappingSource(TypeMappingSourceDependencies dependencies)
var clrType = mappingInfo.ClrType;
Check.DebugAssert(clrType != null, "ClrType is null");

var jsonValueReaderWriter = Dependencies.JsonValueReaderWriterSource.FindReaderWriter(clrType);

if (clrType.IsValueType
|| clrType == typeof(string)
|| clrType == typeof(byte[]))
{
return new InMemoryTypeMapping(clrType);
return new InMemoryTypeMapping(
clrType, jsonValueReaderWriter: jsonValueReaderWriter);
}

if (clrType.FullName == "NetTopologySuite.Geometries.Geometry"
Expand All @@ -48,7 +51,8 @@ public InMemoryTypeMappingSource(TypeMappingSourceDependencies dependencies)
return new InMemoryTypeMapping(
clrType,
comparer,
comparer);
comparer,
jsonValueReaderWriter);
}

return base.FindMapping(mappingInfo);
Expand Down
3 changes: 2 additions & 1 deletion src/EFCore.Relational/Storage/BoolTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data;
using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Storage;

Expand All @@ -28,7 +29,7 @@ public class BoolTypeMapping : RelationalTypeMapping
public BoolTypeMapping(
string storeType,
DbType? dbType = System.Data.DbType.Boolean)
: base(storeType, typeof(bool), dbType)
: base(storeType, typeof(bool), dbType, jsonValueReaderWriter: JsonBoolReaderWriter.Instance)
{
}

Expand Down
4 changes: 3 additions & 1 deletion src/EFCore.Relational/Storage/ByteArrayTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Data;
using System.Globalization;
using System.Text;
using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Storage;

Expand Down Expand Up @@ -35,7 +36,8 @@ public ByteArrayTypeMapping(
: base(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(
typeof(byte[])), storeType, StoreTypePostfix.None, dbType, unicode: false, size))
typeof(byte[]), jsonValueReaderWriter: JsonByteArrayReaderWriter.Instance), storeType, StoreTypePostfix.None, dbType,
unicode: false, size))
{
}

Expand Down
3 changes: 2 additions & 1 deletion src/EFCore.Relational/Storage/ByteTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data;
using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Storage;

Expand All @@ -28,7 +29,7 @@ public class ByteTypeMapping : RelationalTypeMapping
public ByteTypeMapping(
string storeType,
DbType? dbType = System.Data.DbType.Byte)
: base(storeType, typeof(byte), dbType)
: base(storeType, typeof(byte), dbType, jsonValueReaderWriter: JsonByteReaderWriter.Instance)
{
}

Expand Down
3 changes: 2 additions & 1 deletion src/EFCore.Relational/Storage/CharTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data;
using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Storage;

Expand All @@ -28,7 +29,7 @@ public class CharTypeMapping : RelationalTypeMapping
public CharTypeMapping(
string storeType,
DbType? dbType = System.Data.DbType.String)
: base(storeType, typeof(char), dbType)
: base(storeType, typeof(char), dbType, jsonValueReaderWriter: JsonCharReaderWriter.Instance)
{
}

Expand Down
3 changes: 2 additions & 1 deletion src/EFCore.Relational/Storage/DateOnlyTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data;
using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Storage;

Expand Down Expand Up @@ -30,7 +31,7 @@ public class DateOnlyTypeMapping : RelationalTypeMapping
public DateOnlyTypeMapping(
string storeType,
DbType? dbType = System.Data.DbType.Date)
: base(storeType, typeof(DateOnly), dbType)
: base(storeType, typeof(DateOnly), dbType, jsonValueReaderWriter: JsonDateOnlyReaderWriter.Instance)
{
}

Expand Down
3 changes: 2 additions & 1 deletion src/EFCore.Relational/Storage/DateTimeOffsetTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data;
using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Storage;

Expand Down Expand Up @@ -30,7 +31,7 @@ public class DateTimeOffsetTypeMapping : RelationalTypeMapping
public DateTimeOffsetTypeMapping(
string storeType,
DbType? dbType = System.Data.DbType.DateTimeOffset)
: base(storeType, typeof(DateTimeOffset), dbType)
: base(storeType, typeof(DateTimeOffset), dbType, jsonValueReaderWriter: JsonDateTimeOffsetReaderWriter.Instance)
{
}

Expand Down
3 changes: 2 additions & 1 deletion src/EFCore.Relational/Storage/DateTimeTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data;
using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Storage;

Expand Down Expand Up @@ -30,7 +31,7 @@ public class DateTimeTypeMapping : RelationalTypeMapping
public DateTimeTypeMapping(
string storeType,
DbType? dbType = System.Data.DbType.DateTime)
: base(storeType, typeof(DateTime), dbType)
: base(storeType, typeof(DateTime), dbType, jsonValueReaderWriter: JsonDateTimeReaderWriter.Instance)
{
}

Expand Down
4 changes: 3 additions & 1 deletion src/EFCore.Relational/Storage/DecimalTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Data;
using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Storage;

Expand Down Expand Up @@ -34,7 +35,8 @@ public DecimalTypeMapping(
DbType? dbType = System.Data.DbType.Decimal,
int? precision = null,
int? scale = null)
: base(storeType, typeof(decimal), dbType, precision: precision, scale: scale)
: base(
storeType, typeof(decimal), dbType, precision: precision, scale: scale, jsonValueReaderWriter: JsonDecimalReaderWriter.Instance)
{
}

Expand Down
3 changes: 2 additions & 1 deletion src/EFCore.Relational/Storage/DoubleTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Data;
using System.Globalization;
using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Storage;

Expand All @@ -29,7 +30,7 @@ public class DoubleTypeMapping : RelationalTypeMapping
public DoubleTypeMapping(
string storeType,
DbType? dbType = System.Data.DbType.Double)
: base(storeType, typeof(double), dbType)
: base(storeType, typeof(double), dbType, jsonValueReaderWriter: JsonDoubleReaderWriter.Instance)
{
}

Expand Down
3 changes: 2 additions & 1 deletion src/EFCore.Relational/Storage/FloatTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Data;
using System.Globalization;
using Microsoft.EntityFrameworkCore.Storage.Json;

namespace Microsoft.EntityFrameworkCore.Storage;

Expand All @@ -29,7 +30,7 @@ public class FloatTypeMapping : RelationalTypeMapping
public FloatTypeMapping(
string storeType,
DbType? dbType = System.Data.DbType.Single)
: base(storeType, typeof(float), dbType)
: base(storeType, typeof(float), dbType, jsonValueReaderWriter: JsonFloatReaderWriter.Instance)
{
}

Expand Down
Loading

0 comments on commit 9ab8043

Please sign in to comment.