Merge remote-tracking branch 'remotes/upstream/api-migration' into api-channel

# Conflicts:
#	Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
This commit is contained in:
crobibero 2020-06-03 09:38:49 -06:00
commit 88b6c26472
11 changed files with 381 additions and 220 deletions

View File

@ -0,0 +1,125 @@
#nullable enable
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Models.ConfigurationDtos;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Jellyfin.Api.Controllers
{
/// <summary>
/// Configuration Controller.
/// </summary>
[Route("System")]
[Authorize]
public class ConfigurationController : BaseJellyfinApiController
{
private readonly IServerConfigurationManager _configurationManager;
private readonly IMediaEncoder _mediaEncoder;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationController"/> class.
/// </summary>
/// <param name="configurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
public ConfigurationController(
IServerConfigurationManager configurationManager,
IMediaEncoder mediaEncoder)
{
_configurationManager = configurationManager;
_mediaEncoder = mediaEncoder;
}
/// <summary>
/// Gets application configuration.
/// </summary>
/// <response code="200">Application configuration returned.</response>
/// <returns>Application configuration.</returns>
[HttpGet("Configuration")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<ServerConfiguration> GetConfiguration()
{
return _configurationManager.Configuration;
}
/// <summary>
/// Updates application configuration.
/// </summary>
/// <param name="configuration">Configuration.</param>
/// <response code="200">Configuration updated.</response>
/// <returns>Update status.</returns>
[HttpPost("Configuration")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult UpdateConfiguration([FromBody, BindRequired] ServerConfiguration configuration)
{
_configurationManager.ReplaceConfiguration(configuration);
return Ok();
}
/// <summary>
/// Gets a named configuration.
/// </summary>
/// <param name="key">Configuration key.</param>
/// <response code="200">Configuration returned.</response>
/// <returns>Configuration.</returns>
[HttpGet("Configuration/{Key}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<object> GetNamedConfiguration([FromRoute] string key)
{
return _configurationManager.GetConfiguration(key);
}
/// <summary>
/// Updates named configuration.
/// </summary>
/// <param name="key">Configuration key.</param>
/// <response code="200">Named configuration updated.</response>
/// <returns>Update status.</returns>
[HttpPost("Configuration/{Key}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> UpdateNamedConfiguration([FromRoute] string key)
{
var configurationType = _configurationManager.GetConfigurationType(key);
var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType);
_configurationManager.SaveConfiguration(key, configuration);
return Ok();
}
/// <summary>
/// Gets a default MetadataOptions object.
/// </summary>
/// <response code="200">Metadata options returned.</response>
/// <returns>Default MetadataOptions.</returns>
[HttpGet("Configuration/MetadataOptions/Default")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<MetadataOptions> GetDefaultMetadataOptions()
{
return new MetadataOptions();
}
/// <summary>
/// Updates the path to the media encoder.
/// </summary>
/// <param name="mediaEncoderPath">Media encoder path form body.</param>
/// <response code="200">Media encoder path updated.</response>
/// <returns>Status.</returns>
[HttpPost("MediaEncoder/Path")]
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult UpdateMediaEncoderPath([FromForm, BindRequired] MediaEncoderPathDto mediaEncoderPath)
{
_mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType);
return Ok();
}
}
}

View File

@ -5,6 +5,7 @@ using Jellyfin.Api.Models.StartupDtos;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers
@ -30,22 +31,28 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
/// Api endpoint for completing the startup wizard.
/// Completes the startup wizard.
/// </summary>
/// <response code="200">Startup wizard completed.</response>
/// <returns>An <see cref="OkResult"/> indicating success.</returns>
[HttpPost("Complete")]
public void CompleteWizard()
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult CompleteWizard()
{
_config.Configuration.IsStartupWizardCompleted = true;
_config.SetOptimalValues();
_config.SaveConfiguration();
return Ok();
}
/// <summary>
/// Endpoint for getting the initial startup wizard configuration.
/// Gets the initial startup wizard configuration.
/// </summary>
/// <returns>The initial startup wizard configuration.</returns>
/// <response code="200">Initial startup wizard configuration retrieved.</response>
/// <returns>An <see cref="OkResult"/> containing the initial startup wizard configuration.</returns>
[HttpGet("Configuration")]
public StartupConfigurationDto GetStartupConfiguration()
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<StartupConfigurationDto> GetStartupConfiguration()
{
var result = new StartupConfigurationDto
{
@ -58,13 +65,16 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
/// Endpoint for updating the initial startup wizard configuration.
/// Sets the initial startup wizard configuration.
/// </summary>
/// <param name="uiCulture">The UI language culture.</param>
/// <param name="metadataCountryCode">The metadata country code.</param>
/// <param name="preferredMetadataLanguage">The preferred language for metadata.</param>
/// <response code="200">Configuration saved.</response>
/// <returns>An <see cref="OkResult"/> indicating success.</returns>
[HttpPost("Configuration")]
public void UpdateInitialConfiguration(
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult UpdateInitialConfiguration(
[FromForm] string uiCulture,
[FromForm] string metadataCountryCode,
[FromForm] string preferredMetadataLanguage)
@ -73,43 +83,51 @@ namespace Jellyfin.Api.Controllers
_config.Configuration.MetadataCountryCode = metadataCountryCode;
_config.Configuration.PreferredMetadataLanguage = preferredMetadataLanguage;
_config.SaveConfiguration();
return Ok();
}
/// <summary>
/// Endpoint for (dis)allowing remote access and UPnP.
/// Sets remote access and UPnP.
/// </summary>
/// <param name="enableRemoteAccess">Enable remote access.</param>
/// <param name="enableAutomaticPortMapping">Enable UPnP.</param>
/// <response code="200">Configuration saved.</response>
/// <returns>An <see cref="OkResult"/> indicating success.</returns>
[HttpPost("RemoteAccess")]
public void SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping)
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping)
{
_config.Configuration.EnableRemoteAccess = enableRemoteAccess;
_config.Configuration.EnableUPnP = enableAutomaticPortMapping;
_config.SaveConfiguration();
return Ok();
}
/// <summary>
/// Endpoint for returning the first user.
/// Gets the first user.
/// </summary>
/// <response code="200">Initial user retrieved.</response>
/// <returns>The first user.</returns>
[HttpGet("User")]
public StartupUserDto GetFirstUser()
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<StartupUserDto> GetFirstUser()
{
var user = _userManager.Users.First();
return new StartupUserDto
{
Name = user.Name,
Password = user.Password
};
return new StartupUserDto { Name = user.Name, Password = user.Password };
}
/// <summary>
/// Endpoint for updating the user name and password.
/// Sets the user name and password.
/// </summary>
/// <param name="startupUserDto">The DTO containing username and password.</param>
/// <returns>The async task.</returns>
/// <response code="200">Updated user name and password.</response>
/// <returns>
/// A <see cref="Task" /> that represents the asynchronous update operation.
/// The task result contains an <see cref="OkResult"/> indicating success.
/// </returns>
[HttpPost("User")]
public async Task UpdateUser([FromForm] StartupUserDto startupUserDto)
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> UpdateUser([FromForm] StartupUserDto startupUserDto)
{
var user = _userManager.Users.First();
@ -121,6 +139,8 @@ namespace Jellyfin.Api.Controllers
{
await _userManager.ChangePassword(user, startupUserDto.Password).ConfigureAwait(false);
}
return Ok();
}
}
}

View File

@ -0,0 +1,18 @@
namespace Jellyfin.Api.Models.ConfigurationDtos
{
/// <summary>
/// Media Encoder Path Dto.
/// </summary>
public class MediaEncoderPathDto
{
/// <summary>
/// Gets or sets media encoder path.
/// </summary>
public string Path { get; set; }
/// <summary>
/// Gets or sets media encoder path type.
/// </summary>
public string PathType { get; set; }
}
}

View File

@ -10,6 +10,7 @@ using Jellyfin.Api.Auth.RequiresElevationPolicy;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Controllers;
using Jellyfin.Server.Formatters;
using MediaBrowser.Common.Json;
using MediaBrowser.Model.Entities;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
@ -83,8 +84,20 @@ namespace Jellyfin.Server.Extensions
.AddApplicationPart(typeof(StartupController).Assembly)
.AddJsonOptions(options =>
{
// Setting the naming policy to null leaves the property names as-is when serializing objects to JSON.
options.JsonSerializerOptions.PropertyNamingPolicy = null;
// Update all properties that are set in JsonDefaults
var jsonOptions = JsonDefaults.PascalCase;
// From JsonDefaults
options.JsonSerializerOptions.ReadCommentHandling = jsonOptions.ReadCommentHandling;
options.JsonSerializerOptions.WriteIndented = jsonOptions.WriteIndented;
options.JsonSerializerOptions.Converters.Clear();
foreach (var converter in jsonOptions.Converters)
{
options.JsonSerializerOptions.Converters.Add(converter);
}
// From JsonDefaults.PascalCase
options.JsonSerializerOptions.PropertyNamingPolicy = jsonOptions.PropertyNamingPolicy;
})
.AddControllersAsServices();
}
@ -98,7 +111,7 @@ namespace Jellyfin.Server.Extensions
{
return serviceCollection.AddSwaggerGen(c =>
{
c.SwaggerDoc("api-docs", new OpenApiInfo { Title = "Jellyfin API" });
c.SwaggerDoc("api-docs", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" });
// Add all xml doc files to swagger generator.
var xmlFiles = Directory.GetFiles(
@ -119,16 +132,17 @@ namespace Jellyfin.Server.Extensions
c.CustomOperationIds(description =>
description.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null);
// Add types not supported by System.Text.Json
// TODO: Remove this once these types are supported by System.Text.Json and Swashbuckle
// See: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1667
c.MapSwaggerGenTypes();
// TODO - remove when all types are supported in System.Text.Json
c.AddSwaggerTypeMappings();
});
}
private static void MapSwaggerGenTypes(this SwaggerGenOptions options)
private static void AddSwaggerTypeMappings(this SwaggerGenOptions options)
{
// BaseItemDto.ImageTags
/*
* TODO remove when System.Text.Json supports non-string keys.
* Used in Jellyfin.Api.Controller.GetChannels.
*/
options.MapType<Dictionary<ImageType, string>>(() =>
new OpenApiSchema
{

View File

@ -1,4 +1,4 @@
using Jellyfin.Server.Models;
using MediaBrowser.Common.Json;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
@ -12,7 +12,7 @@ namespace Jellyfin.Server.Formatters
/// <summary>
/// Initializes a new instance of the <see cref="CamelCaseJsonProfileFormatter"/> class.
/// </summary>
public CamelCaseJsonProfileFormatter() : base(JsonOptions.CamelCase)
public CamelCaseJsonProfileFormatter() : base(JsonDefaults.CamelCase)
{
SupportedMediaTypes.Clear();
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"CamelCase\""));

View File

@ -1,4 +1,4 @@
using Jellyfin.Server.Models;
using MediaBrowser.Common.Json;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
@ -12,7 +12,7 @@ namespace Jellyfin.Server.Formatters
/// <summary>
/// Initializes a new instance of the <see cref="PascalCaseJsonProfileFormatter"/> class.
/// </summary>
public PascalCaseJsonProfileFormatter() : base(JsonOptions.PascalCase)
public PascalCaseJsonProfileFormatter() : base(JsonDefaults.PascalCase)
{
SupportedMediaTypes.Clear();
// Add application/json for default formatter

View File

@ -1,41 +0,0 @@
using System.Text.Json;
namespace Jellyfin.Server.Models
{
/// <summary>
/// Json Options.
/// </summary>
public static class JsonOptions
{
/// <summary>
/// Gets CamelCase json options.
/// </summary>
public static JsonSerializerOptions CamelCase
{
get
{
var options = DefaultJsonOptions;
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
return options;
}
}
/// <summary>
/// Gets PascalCase json options.
/// </summary>
public static JsonSerializerOptions PascalCase
{
get
{
var options = DefaultJsonOptions;
options.PropertyNamingPolicy = null;
return options;
}
}
/// <summary>
/// Gets base Json Serializer Options.
/// </summary>
private static JsonSerializerOptions DefaultJsonOptions => new JsonSerializerOptions();
}
}

View File

@ -1,146 +0,0 @@
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api
{
/// <summary>
/// Class GetConfiguration
/// </summary>
[Route("/System/Configuration", "GET", Summary = "Gets application configuration")]
[Authenticated]
public class GetConfiguration : IReturn<ServerConfiguration>
{
}
[Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")]
[Authenticated(AllowBeforeStartupWizard = true)]
public class GetNamedConfiguration
{
[ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Key { get; set; }
}
/// <summary>
/// Class UpdateConfiguration
/// </summary>
[Route("/System/Configuration", "POST", Summary = "Updates application configuration")]
[Authenticated(Roles = "Admin")]
public class UpdateConfiguration : ServerConfiguration, IReturnVoid
{
}
[Route("/System/Configuration/{Key}", "POST", Summary = "Updates named configuration")]
[Authenticated(Roles = "Admin")]
public class UpdateNamedConfiguration : IReturnVoid, IRequiresRequestStream
{
[ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Key { get; set; }
public Stream RequestStream { get; set; }
}
[Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")]
[Authenticated(Roles = "Admin")]
public class GetDefaultMetadataOptions : IReturn<MetadataOptions>
{
}
[Route("/System/MediaEncoder/Path", "POST", Summary = "Updates the path to the media encoder")]
[Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)]
public class UpdateMediaEncoderPath : IReturnVoid
{
[ApiMember(Name = "Path", Description = "Path", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Path { get; set; }
[ApiMember(Name = "PathType", Description = "PathType", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string PathType { get; set; }
}
public class ConfigurationService : BaseApiService
{
/// <summary>
/// The _json serializer
/// </summary>
private readonly IJsonSerializer _jsonSerializer;
/// <summary>
/// The _configuration manager
/// </summary>
private readonly IServerConfigurationManager _configurationManager;
private readonly IMediaEncoder _mediaEncoder;
public ConfigurationService(
ILogger<ConfigurationService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IJsonSerializer jsonSerializer,
IServerConfigurationManager configurationManager,
IMediaEncoder mediaEncoder)
: base(logger, serverConfigurationManager, httpResultFactory)
{
_jsonSerializer = jsonSerializer;
_configurationManager = configurationManager;
_mediaEncoder = mediaEncoder;
}
public void Post(UpdateMediaEncoderPath request)
{
_mediaEncoder.UpdateEncoderPath(request.Path, request.PathType);
}
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
public object Get(GetConfiguration request)
{
return ToOptimizedResult(_configurationManager.Configuration);
}
public object Get(GetNamedConfiguration request)
{
var result = _configurationManager.GetConfiguration(request.Key);
return ToOptimizedResult(result);
}
/// <summary>
/// Posts the specified configuraiton.
/// </summary>
/// <param name="request">The request.</param>
public void Post(UpdateConfiguration request)
{
// Silly, but we need to serialize and deserialize or the XmlSerializer will write the xml with an element name of UpdateConfiguration
var json = _jsonSerializer.SerializeToString(request);
var config = _jsonSerializer.DeserializeFromString<ServerConfiguration>(json);
_configurationManager.ReplaceConfiguration(config);
}
public async Task Post(UpdateNamedConfiguration request)
{
var key = GetPathValue(2).ToString();
var configurationType = _configurationManager.GetConfigurationType(key);
var configuration = await _jsonSerializer.DeserializeFromStreamAsync(request.RequestStream, configurationType).ConfigureAwait(false);
_configurationManager.SaveConfiguration(key, configuration);
}
public object Get(GetDefaultMetadataOptions request)
{
return ToOptimizedResult(new MetadataOptions());
}
}
}

View File

@ -0,0 +1,78 @@
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
{
/// <summary>
/// Converter for Dictionaries without string key.
/// TODO This can be removed when System.Text.Json supports Dictionaries with non-string keys.
/// </summary>
/// <typeparam name="TKey">Type of key.</typeparam>
/// <typeparam name="TValue">Type of value.</typeparam>
internal sealed class JsonNonStringKeyDictionaryConverter<TKey, TValue> : JsonConverter<IDictionary<TKey, TValue>>
{
/// <summary>
/// Read JSON.
/// </summary>
/// <param name="reader">The Utf8JsonReader.</param>
/// <param name="typeToConvert">The type to convert.</param>
/// <param name="options">The json serializer options.</param>
/// <returns>Typed dictionary.</returns>
/// <exception cref="NotSupportedException"></exception>
public override IDictionary<TKey, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var convertedType = typeof(Dictionary<,>).MakeGenericType(typeof(string), typeToConvert.GenericTypeArguments[1]);
var value = JsonSerializer.Deserialize(ref reader, convertedType, options);
var instance = (Dictionary<TKey, TValue>)Activator.CreateInstance(
typeToConvert,
BindingFlags.Instance | BindingFlags.Public,
null,
null,
CultureInfo.CurrentCulture);
var enumerator = (IEnumerator)convertedType.GetMethod("GetEnumerator")!.Invoke(value, null);
var parse = typeof(TKey).GetMethod(
"Parse",
0,
BindingFlags.Public | BindingFlags.Static,
null,
CallingConventions.Any,
new[] { typeof(string) },
null);
if (parse == null)
{
throw new NotSupportedException($"{typeof(TKey)} as TKey in IDictionary<TKey, TValue> is not supported.");
}
while (enumerator.MoveNext())
{
var element = (KeyValuePair<string?, TValue>)enumerator.Current;
instance.Add((TKey)parse.Invoke(null, new[] { (object?) element.Key }), element.Value);
}
return instance;
}
/// <summary>
/// Write dictionary as Json.
/// </summary>
/// <param name="writer">The Utf8JsonWriter.</param>
/// <param name="value">The dictionary value.</param>
/// <param name="options">The Json serializer options.</param>
public override void Write(Utf8JsonWriter writer, IDictionary<TKey, TValue> value, JsonSerializerOptions options)
{
var convertedDictionary = new Dictionary<string?, TValue>(value.Count);
foreach (var (k, v) in value)
{
convertedDictionary[k?.ToString()] = v;
}
JsonSerializer.Serialize(writer, convertedDictionary, options);
}
}
}

View File

@ -0,0 +1,60 @@
#nullable enable
using System;
using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
{
/// <summary>
/// https://github.com/dotnet/runtime/issues/30524#issuecomment-524619972.
/// TODO This can be removed when System.Text.Json supports Dictionaries with non-string keys.
/// </summary>
internal sealed class JsonNonStringKeyDictionaryConverterFactory : JsonConverterFactory
{
/// <summary>
/// Only convert objects that implement IDictionary and do not have string keys.
/// </summary>
/// <param name="typeToConvert">Type convert.</param>
/// <returns>Conversion ability.</returns>
public override bool CanConvert(Type typeToConvert)
{
if (!typeToConvert.IsGenericType)
{
return false;
}
// Let built in converter handle string keys
if (typeToConvert.GenericTypeArguments[0] == typeof(string))
{
return false;
}
// Only support objects that implement IDictionary
return typeToConvert.GetInterface(nameof(IDictionary)) != null;
}
/// <summary>
/// Create converter for generic dictionary type.
/// </summary>
/// <param name="typeToConvert">Type to convert.</param>
/// <param name="options">Json serializer options.</param>
/// <returns>JsonConverter for given type.</returns>
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var converterType = typeof(JsonNonStringKeyDictionaryConverter<,>)
.MakeGenericType(typeToConvert.GenericTypeArguments[0], typeToConvert.GenericTypeArguments[1]);
var converter = (JsonConverter)Activator.CreateInstance(
converterType,
BindingFlags.Instance | BindingFlags.Public,
null,
null,
CultureInfo.CurrentCulture);
return converter;
}
}
}

View File

@ -12,10 +12,16 @@ namespace MediaBrowser.Common.Json
/// <summary>
/// Gets the default <see cref="JsonSerializerOptions" /> options.
/// </summary>
/// <remarks>
/// When changing these options, update
/// Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
/// -> AddJellyfinApi
/// -> AddJsonOptions
/// </remarks>
/// <returns>The default <see cref="JsonSerializerOptions" /> options.</returns>
public static JsonSerializerOptions GetOptions()
{
var options = new JsonSerializerOptions()
var options = new JsonSerializerOptions
{
ReadCommentHandling = JsonCommentHandling.Disallow,
WriteIndented = false
@ -23,8 +29,35 @@ namespace MediaBrowser.Common.Json
options.Converters.Add(new JsonGuidConverter());
options.Converters.Add(new JsonStringEnumConverter());
options.Converters.Add(new JsonNonStringKeyDictionaryConverterFactory());
return options;
}
/// <summary>
/// Gets CamelCase json options.
/// </summary>
public static JsonSerializerOptions CamelCase
{
get
{
var options = GetOptions();
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
return options;
}
}
/// <summary>
/// Gets PascalCase json options.
/// </summary>
public static JsonSerializerOptions PascalCase
{
get
{
var options = GetOptions();
options.PropertyNamingPolicy = null;
return options;
}
}
}
}