Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/Umbraco.Core/Serialization/IJsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ public interface IJsonSerializer
/// </returns>
string Serialize(object? input);


/// <summary>
/// Parses the text representing a single JSON value into an instance of the type specified by a generic type parameter.
/// </summary>
Expand Down
17 changes: 17 additions & 0 deletions src/Umbraco.Core/Serialization/IJsonSerializerEncoderFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Text.Encodings.Web;

namespace Umbraco.Cms.Core.Serialization;

/// <summary>
/// Provides a factory method for creating a <see cref="JavaScriptEncoder"/> for use in instantiating JSON serializers.
/// </summary>
public interface IJsonSerializerEncoderFactory
{
/// <summary>
/// Creates a <see cref="JavaScriptEncoder"/> for use in the serialization of configuration editor JSON.
/// </summary>
/// <typeparam name="TSerializer">The type of the serializer for which the encoder is being created.</typeparam>
/// <returns>A <see cref="JavaScriptEncoder"/> instance.</returns>
JavaScriptEncoder CreateEncoder<TSerializer>()
where TSerializer : IJsonSerializer;
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@

builder.Services.AddSingleton<IJsonSerializer, SystemTextJsonSerializer>();
builder.Services.AddSingleton<IConfigurationEditorJsonSerializer, SystemTextConfigurationEditorJsonSerializer>();
builder.Services.AddUnique<IJsonSerializerEncoderFactory, DefaultJsonSerializerEncoderFactory>();

Check warning on line 127 in src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ Getting worse: Large Method

AddCoreInitialServices increases from 119 to 120 lines of code, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
builder.Services.AddUnique<IWebhookJsonSerializer, SystemTextWebhookJsonSerializer>();

// register database builder
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Umbraco.Cms.Core.Serialization;

namespace Umbraco.Cms.Infrastructure.Serialization;

/// <inheritdoc />
public sealed class DefaultJsonSerializerEncoderFactory : IJsonSerializerEncoderFactory
{
/// <inheritdoc />
public JavaScriptEncoder CreateEncoder<TSerializer>()
where TSerializer : IJsonSerializer
=> JavaScriptEncoder.Create(UnicodeRanges.BasicLatin);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Serialization;

Expand All @@ -14,11 +16,24 @@ public sealed class SystemTextConfigurationEditorJsonSerializer : SystemTextJson
/// <summary>
/// Initializes a new instance of the <see cref="SystemTextConfigurationEditorJsonSerializer" /> class.
/// </summary>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public SystemTextConfigurationEditorJsonSerializer()
: this(
StaticServiceProvider.Instance.GetRequiredService<IJsonSerializerEncoderFactory>())
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SystemTextConfigurationEditorJsonSerializer" /> class.
/// </summary>
public SystemTextConfigurationEditorJsonSerializer(IJsonSerializerEncoderFactory jsonSerializerEncoderFactory)
: base(jsonSerializerEncoderFactory)
=> _jsonSerializerOptions = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,

Encoder = jsonSerializerEncoderFactory.CreateEncoder<SystemTextConfigurationEditorJsonSerializer>(),

// In some cases, configs aren't camel cased in the DB, so we have to resort to case insensitive
// property name resolving when creating configuration objects (deserializing DB configs).
PropertyNameCaseInsensitive = true,
Expand All @@ -40,6 +55,7 @@ public SystemTextConfigurationEditorJsonSerializer()
.WithAddedModifier(UseAttributeConfiguredPropertyNames()),
};

/// <inheritdoc/>
protected override JsonSerializerOptions JsonSerializerOptions => _jsonSerializerOptions;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Serialization;

namespace Umbraco.Cms.Infrastructure.Serialization;

Expand All @@ -9,12 +12,26 @@ public sealed class SystemTextJsonSerializer : SystemTextJsonSerializerBase
private readonly JsonSerializerOptions _jsonSerializerOptions;

/// <summary>
/// Initializes a new instance of the <see cref="SystemTextJsonSerializer" /> class.
/// Initializes a new instance of the <see cref="SystemTextConfigurationEditorJsonSerializer" /> class.
/// </summary>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public SystemTextJsonSerializer()
: this(
StaticServiceProvider.Instance.GetRequiredService<IJsonSerializerEncoderFactory>())
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SystemTextJsonSerializer" /> class.
/// </summary>
public SystemTextJsonSerializer(IJsonSerializerEncoderFactory jsonSerializerEncoderFactory)
: base(jsonSerializerEncoderFactory)
=> _jsonSerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,

Encoder = jsonSerializerEncoderFactory.CreateEncoder<SystemTextJsonSerializer>(),

Converters =
{
new JsonStringEnumConverter(),
Expand All @@ -25,5 +42,6 @@ public SystemTextJsonSerializer()
}
};

/// <inheritdoc/>
protected override JsonSerializerOptions JsonSerializerOptions => _jsonSerializerOptions;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,38 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Extensions;

namespace Umbraco.Cms.Infrastructure.Serialization;

public abstract class SystemTextJsonSerializerBase : IJsonSerializer
{
private readonly IJsonSerializerEncoderFactory _jsonSerializerEncoderFactory;

/// <summary>
/// Initializes a new instance of the <see cref="SystemTextJsonSerializerBase" /> class.
/// </summary>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
protected SystemTextJsonSerializerBase()
: this(
StaticServiceProvider.Instance.GetRequiredService<IJsonSerializerEncoderFactory>())
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SystemTextJsonSerializerBase"/> class.
/// </summary>
/// <param name="jsonSerializerEncoderFactory">The <see cref="IJsonSerializerEncoderFactory"/> for creating the <see cref="JavaScriptEncoder"/>.</param>
protected SystemTextJsonSerializerBase(IJsonSerializerEncoderFactory jsonSerializerEncoderFactory)
=> _jsonSerializerEncoderFactory = jsonSerializerEncoderFactory;

/// <summary>
/// Gets the <see cref="System.Text.Json.JsonSerializerOptions"/>.
/// </summary>
protected abstract JsonSerializerOptions JsonSerializerOptions { get; }

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Text.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Serialization;

namespace Umbraco.Cms.Infrastructure.Serialization;
Expand All @@ -10,22 +12,37 @@ public sealed class SystemTextWebhookJsonSerializer : SystemTextJsonSerializerBa
private readonly JsonSerializerOptions _jsonSerializerOptions;

/// <summary>
/// Initializes a new instance of the <see cref="SystemTextWebhookJsonSerializer" /> class.
/// Initializes a new instance of the <see cref="SystemTextConfigurationEditorJsonSerializer" /> class.
/// </summary>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public SystemTextWebhookJsonSerializer()
: this(
StaticServiceProvider.Instance.GetRequiredService<IJsonSerializerEncoderFactory>())
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SystemTextWebhookJsonSerializer" /> class.
/// </summary>
public SystemTextWebhookJsonSerializer(IJsonSerializerEncoderFactory jsonSerializerEncoderFactory)
: base(jsonSerializerEncoderFactory)
=> _jsonSerializerOptions = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,

Encoder = jsonSerializerEncoderFactory.CreateEncoder<SystemTextWebhookJsonSerializer>(),

Converters =
{
new JsonStringEnumConverter(),
new JsonUdiConverter(),
new JsonUdiRangeConverter(),
new JsonObjectConverter(), // Required for block editor values
new JsonBlockValueConverter()
new JsonBlockValueConverter(),
},
TypeInfoResolver = new WebhookJsonTypeResolver(),
};

/// <inheritdoc/>
protected override JsonSerializerOptions JsonSerializerOptions => _jsonSerializerOptions;
}
2 changes: 1 addition & 1 deletion tests/Umbraco.TestData/UmbracoTestDataController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ private IDataType GetOrCreateDataType(string name, string editorAlias)
throw new InvalidOperationException($"No {editorAlias} editor found");
}

var serializer = new SystemTextConfigurationEditorJsonSerializer();
var serializer = new SystemTextConfigurationEditorJsonSerializer(new DefaultJsonSerializerEncoderFactory());

dt = new DataType(editor, serializer)
{
Expand Down
2 changes: 1 addition & 1 deletion tests/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public override DataType Build()
var creatorId = _creatorId ?? 1;
var databaseType = _databaseType ?? ValueStorageType.Ntext;
var sortOrder = _sortOrder ?? 0;
var serializer = new SystemTextConfigurationEditorJsonSerializer();
var serializer = new SystemTextConfigurationEditorJsonSerializer(new DefaultJsonSerializerEncoderFactory());

var dataType = new DataType(editor, serializer, parentId)
{
Expand Down
2 changes: 1 addition & 1 deletion tests/Umbraco.Tests.Common/TestHelperBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public IScopeProvider ScopeProvider
}
}

public IJsonSerializer JsonSerializer { get; } = new SystemTextJsonSerializer();
public IJsonSerializer JsonSerializer { get; } = new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory());

public IVariationContextAccessor VariationContextAccessor { get; } = new TestVariationContextAccessor();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static DataValueEditor CreateDataValueEditor(string name)

return new DataValueEditor(
Mock.Of<IShortStringHelper>(),
new SystemTextJsonSerializer(),
new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()),
Mock.Of<IIOHelper>(),
new DataEditorAttribute(name) { ValueType = valueType });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public void Can_Perform_Delete_When_Assigned_To_Doc()
{
var templateRepository = CreateRepository(provider);
var globalSettings = new GlobalSettings();
var serializer = new SystemTextJsonSerializer();
var serializer = new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory());
var tagRepository = new TagRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger<TagRepository>());
var commonRepository =
new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches, ShortStringHelper);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ private UserRepository CreateRepository(ICoreScopeProvider provider)
Mappers,
Options.Create(GlobalSettings),
Options.Create(new UserPasswordConfigurationSettings()),
new SystemTextJsonSerializer(),
new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()),
mockRuntimeState.Object,
PermissionMappers);
return repository;
Expand Down Expand Up @@ -162,7 +162,7 @@ public void Can_Perform_Delete_On_UserRepository()
Mock.Of<IMapperCollection>(),
Options.Create(GlobalSettings),
Options.Create(new UserPasswordConfigurationSettings()),
new SystemTextJsonSerializer(),
new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()),
mockRuntimeState.Object,
PermissionMappers);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ public async Task SingleItemsOnlyRepositoryCachePolicy(bool complete)
public class LocalServerMessenger : ServerMessengerBase
{
public LocalServerMessenger()
: base(false, new SystemTextJsonSerializer())
: base(false, new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()))
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2089,7 +2089,7 @@ public void ContentRemembers()
internal sealed class LocalServerMessenger : ServerMessengerBase
{
public LocalServerMessenger()
: base(false, new SystemTextJsonSerializer())
: base(false, new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()))
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
//
// protected static DataType[] GetDefaultDataTypes()
// {
// var serializer = new SystemTextConfigurationEditorJsonSerializer();
// var serializer = new SystemTextConfigurationEditorJsonSerializer(new DefaultConfigurationEditorJsonSerializerEncoderFactory());
//
// // create data types, property types and content types
// var dataType =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ public void ImageCropperValueConverter_ConvertsValueToImageCropperValue()
var publishedPropertyType = new Mock<IPublishedPropertyType>();
publishedPropertyType.SetupGet(p => p.DataType).Returns(publishedDataType);

var valueConverter = new ImageCropperValueConverter(Mock.Of<ILogger<ImageCropperValueConverter>>(), new SystemTextJsonSerializer());
var valueConverter = new ImageCropperValueConverter(Mock.Of<ILogger<ImageCropperValueConverter>>(), new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()));
Assert.AreEqual(typeof(ApiImageCropperValue), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType.Object));

var serializer = new SystemTextJsonSerializer();
var serializer = new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory());
var source = serializer.Serialize(new ImageCropperValue
{
Src = "/some/file.jpg",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes
{
private MediaPickerWithCropsValueConverter MediaPickerWithCropsValueConverter()
{
var serializer = new SystemTextJsonSerializer();
var serializer = new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory());
var publishedValueFallback = Mock.Of<IPublishedValueFallback>();
var apiUrlProvider = new ApiMediaUrlProvider(PublishedUrlProvider);
var apiMediaWithCropsBuilder = new ApiMediaWithCropsBuilder(
Expand All @@ -40,7 +40,7 @@ public void MediaPickerWithCropsValueConverter_InSingleMode_ConvertsValueToColle
var publishedPropertyType = SetupMediaPropertyType(false);
var mediaKey = SetupMedia("My media", ".jpg", 200, 400, "My alt text", 800);

var serializer = new SystemTextJsonSerializer();
var serializer = new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory());

var valueConverter = MediaPickerWithCropsValueConverter();
Assert.AreEqual(typeof(IEnumerable<IApiMediaWithCrops>), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType));
Expand Down Expand Up @@ -84,7 +84,7 @@ public void MediaPickerWithCropsValueConverter_InMultiMode_ConvertsValueToMedias
var mediaKey1 = SetupMedia("My media", ".jpg", 200, 400, "My alt text", 800);
var mediaKey2 = SetupMedia("My other media", ".png", 800, 600, "My other alt text", 200);

var serializer = new SystemTextJsonSerializer();
var serializer = new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory());

var valueConverter = MediaPickerWithCropsValueConverter();
Assert.AreEqual(typeof(IEnumerable<IApiMediaWithCrops>), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType));
Expand Down Expand Up @@ -166,7 +166,7 @@ public void MediaPickerWithCropsValueConverter_MergesMediaCropsWithLocalCrops()
};
var mediaKey = SetupMedia("Some media", ".123", 123, 456, "My alt text", 789, mediaCrops);

var serializer = new SystemTextJsonSerializer();
var serializer = new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory());

var valueConverter = MediaPickerWithCropsValueConverter();
Assert.AreEqual(typeof(IEnumerable<IApiMediaWithCrops>), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType));
Expand Down Expand Up @@ -226,7 +226,7 @@ public void MediaPickerWithCropsValueConverter_LocalCropsAndFocalPointTakesPrece
};
var mediaKey = SetupMedia("Some media", ".123", 123, 456, "My alt text", 789, mediaCrops);

var serializer = new SystemTextJsonSerializer();
var serializer = new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory());

var valueConverter = MediaPickerWithCropsValueConverter();
Assert.AreEqual(typeof(IEnumerable<IApiMediaWithCrops>), valueConverter.GetDeliveryApiPropertyValueType(publishedPropertyType));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,5 +306,5 @@ private MultiUrlPickerValueConverter MultiUrlPickerValueConverter()
CacheManager.Media);
}

private IJsonSerializer Serializer() => new SystemTextJsonSerializer();
private IJsonSerializer Serializer() => new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory());
}
Loading