diff --git a/src/Auth0.AuthenticationApi/FlexibleDateTimeConverter.cs b/src/Auth0.AuthenticationApi/FlexibleDateTimeConverter.cs
index b2533e1d7..4c1814111 100644
--- a/src/Auth0.AuthenticationApi/FlexibleDateTimeConverter.cs
+++ b/src/Auth0.AuthenticationApi/FlexibleDateTimeConverter.cs
@@ -1,41 +1,43 @@
-using System;
-
-using Newtonsoft.Json;
-using Newtonsoft.Json.Converters;
+using System;
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi;
///
-/// A JSON date converter that reads both ISO 8601 and epoch dates.
+/// A JSON date converter that reads both ISO 8601 and epoch (seconds) dates.
///
-internal class FlexibleDateTimeConverter : IsoDateTimeConverter
+internal class FlexibleDateTimeConverter : JsonConverter
{
private static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- throw new NotImplementedException();
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.Null:
+ return null;
+ case JsonTokenType.Number:
+ return Add(Epoch, TimeSpan.FromSeconds(reader.GetInt64()));
+ case JsonTokenType.String:
+ // Prefer STJ's ISO 8601 reader; fall back to a lenient parse for other
+ // formats. An unparseable value yields null rather than throwing and
+ // failing the entire enclosing object's deserialization.
+ if (reader.TryGetDateTime(out var iso))
+ return iso;
+ return DateTime.TryParse(reader.GetString(), CultureInfo.InvariantCulture,
+ DateTimeStyles.RoundtripKind, out var parsed) ? parsed : null;
+ default:
+ return null;
+ }
}
- public override bool CanWrite => false;
-
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
{
- if (reader.Value == null) return null;
-
- return reader.TokenType == JsonToken.Integer
- ? Add(Epoch, TimeSpan.FromSeconds((long)reader.Value))
- : reader.Value;
+ throw new NotImplementedException();
}
- ///
- /// Add a DateTime and a TimeSpan.
- /// The maximum time is DateTime.MaxTime. It is not an error if time + timespan > MaxTime.
- /// Just return MaxTime.
- ///
- /// Initial value.
- /// to add.
- /// as the sum of time and timespan.
private static DateTime Add(DateTime time, TimeSpan timespan)
{
if (timespan == TimeSpan.Zero)
@@ -50,25 +52,15 @@ private static DateTime Add(DateTime time, TimeSpan timespan)
return time + timespan;
}
- ///
- /// Gets the Maximum value for a DateTime specifying kind.
- ///
- /// DateTimeKind to use.
- /// DateTime of specified kind.
private static DateTime GetMaxValue(DateTimeKind kind)
{
return new DateTime(DateTime.MaxValue.Ticks,
kind == DateTimeKind.Unspecified ? DateTimeKind.Utc : kind);
}
- ///
- /// Gets the Minimum value for a DateTime specifying kind.
- ///
- /// DateTimeKind to use.
- /// DateTime of specified kind.
private static DateTime GetMinValue(DateTimeKind kind)
{
return new DateTime(DateTime.MinValue.Ticks,
kind == DateTimeKind.Unspecified ? DateTimeKind.Utc : kind);
}
-}
\ No newline at end of file
+}
diff --git a/src/Auth0.AuthenticationApi/HttpClientAuthenticationConnection.cs b/src/Auth0.AuthenticationApi/HttpClientAuthenticationConnection.cs
index 37474a78e..ef52d6c9d 100644
--- a/src/Auth0.AuthenticationApi/HttpClientAuthenticationConnection.cs
+++ b/src/Auth0.AuthenticationApi/HttpClientAuthenticationConnection.cs
@@ -7,7 +7,8 @@
using System.Threading;
using System.Threading.Tasks;
-using Newtonsoft.Json;
+using System.Text.Json;
+using Auth0.Core.Serialization;
using Auth0.Core.Exceptions;
using Auth0.Core.Http;
@@ -20,8 +21,6 @@ namespace Auth0.AuthenticationApi;
///
public class HttpClientAuthenticationConnection : IAuthenticationConnection, IDisposable
{
- private static readonly JsonSerializerSettings jsonSerializerSettings = new() { NullValueHandling = NullValueHandling.Ignore, DateParseHandling = DateParseHandling.DateTime };
-
private readonly HttpClient httpClient;
private readonly string agentString;
private bool ownHttpClient;
@@ -98,9 +97,13 @@ private async Task SendRequest(HttpRequestMessage request, CancellationTok
internal T DeserializeContent(string content)
{
- return typeof(T) == typeof(string)
- ? (T)(object)content
- : JsonConvert.DeserializeObject(content, jsonSerializerSettings);
+ if (typeof(T) == typeof(string))
+ return (T)(object)content;
+
+ if (string.IsNullOrWhiteSpace(content))
+ return default;
+
+ return JsonSerializer.Deserialize(content, Auth0JsonSerializerOptions.Default);
}
private void ApplyHeaders(HttpRequestMessage request, IDictionary headers)
@@ -125,7 +128,7 @@ private HttpContent BuildMessageContent(object body)
private static HttpContent CreateJsonStringContent(object body)
{
- var json = JsonConvert.SerializeObject(body, jsonSerializerSettings);
+ var json = JsonSerializer.Serialize(body, body.GetType(), Auth0JsonSerializerOptions.Default);
return new StringContent(json, Encoding.UTF8, "application/json");
}
@@ -157,12 +160,12 @@ private static string CreateAgentString()
#endif
var sdkVersion = typeof(HttpClientAuthenticationConnection).GetTypeInfo().Assembly.GetName().Version;
- var agentJson = JsonConvert.SerializeObject(new
+ var agentJson = JsonSerializer.Serialize(new
{
name = "Auth0.Net",
version = sdkVersion.Major + "." + sdkVersion.Minor + "." + sdkVersion.Revision,
env = new { target }
- }, Formatting.None);
+ });
return Utils.Base64UrlEncode(Encoding.UTF8.GetBytes(agentJson));
}
diff --git a/src/Auth0.AuthenticationApi/Models/AccessTokenResponse.cs b/src/Auth0.AuthenticationApi/Models/AccessTokenResponse.cs
index 38ebd7ef9..deb054fdd 100644
--- a/src/Auth0.AuthenticationApi/Models/AccessTokenResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/AccessTokenResponse.cs
@@ -1,5 +1,5 @@
using System.Collections.Generic;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -11,19 +11,19 @@ public class AccessTokenResponse : TokenBase
///
/// Identifier token.
///
- [JsonProperty("id_token")]
+ [JsonPropertyName("id_token")]
public string IdToken { get; set; }
///
/// Expiration time in seconds.
///
- [JsonProperty("expires_in")]
+ [JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
///
/// Refresh token.
///
- [JsonProperty("refresh_token")]
+ [JsonPropertyName("refresh_token")]
public string RefreshToken { get; set; }
public IDictionary> Headers { get; set; }
diff --git a/src/Auth0.AuthenticationApi/Models/ChangePasswordRequest.cs b/src/Auth0.AuthenticationApi/Models/ChangePasswordRequest.cs
index 35791e1f7..79f34c7c5 100644
--- a/src/Auth0.AuthenticationApi/Models/ChangePasswordRequest.cs
+++ b/src/Auth0.AuthenticationApi/Models/ChangePasswordRequest.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -10,6 +10,6 @@ public class ChangePasswordRequest : UserMaintenanceRequestBase
///
/// The organization_id of the Organization associated with the user.
///
- [JsonProperty("organization")]
+ [JsonPropertyName("organization")]
public string Organization { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/Ciba/ClientInitiatedBackchannelAuthorizationResponse.cs b/src/Auth0.AuthenticationApi/Models/Ciba/ClientInitiatedBackchannelAuthorizationResponse.cs
index 848d40bea..cd3a152d8 100644
--- a/src/Auth0.AuthenticationApi/Models/Ciba/ClientInitiatedBackchannelAuthorizationResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/Ciba/ClientInitiatedBackchannelAuthorizationResponse.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models.Ciba;
@@ -10,18 +10,18 @@ public class ClientInitiatedBackchannelAuthorizationResponse
///
/// Unique id of the authorization request. Can be used further to poll for /oauth/token.
///
- [JsonProperty("auth_req_id")]
+ [JsonPropertyName("auth_req_id")]
public string AuthRequestId { get; set; }
///
/// Tells you how many seconds we have until the authentication request expires.
///
- [JsonProperty("expires_in")]
+ [JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
///
/// Tells how many seconds you must leave between poll requests.
///
- [JsonProperty("interval")]
+ [JsonPropertyName("interval")]
public int Interval { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/Ciba/ClientInitiatedBackchannelAuthorizationTokenResponse.cs b/src/Auth0.AuthenticationApi/Models/Ciba/ClientInitiatedBackchannelAuthorizationTokenResponse.cs
index 59c0e0ee8..71c250522 100644
--- a/src/Auth0.AuthenticationApi/Models/Ciba/ClientInitiatedBackchannelAuthorizationTokenResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/Ciba/ClientInitiatedBackchannelAuthorizationTokenResponse.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-
-using Newtonsoft.Json;
+using System.Text.Json;
+using System.Text.Json.Serialization;
using Auth0.AuthenticationApi.Models;
using Auth0.Core.Serialization;
@@ -10,25 +10,25 @@ namespace Auth0.AuthenticationApi.Models.Ciba;
public class ClientInitiatedBackchannelAuthorizationTokenResponse : AccessTokenResponse
{
- [JsonProperty("scope")]
+ [JsonPropertyName("scope")]
public string Scope { get; set; }
///
/// Raw authorization_details JSON returned by the token endpoint as part of a
/// Rich Authorization Requests (RAR) flow. Use for a strongly-typed view.
///
- [JsonProperty("authorization_details")]
+ [JsonPropertyName("authorization_details")]
[JsonConverter(typeof(StringOrObjectAsStringConverter))]
public string AuthorizationDetailsRaw { get; set; }
+ private bool _authorizationDetailsParsed;
+ private IReadOnlyList _authorizationDetails;
+
///
/// Strongly-typed view of the authorization_details returned by the token endpoint.
/// Returns when no authorization details were returned.
///
- private bool _authorizationDetailsParsed;
- private IReadOnlyList _authorizationDetails;
-
- [Newtonsoft.Json.JsonIgnore]
+ [JsonIgnore]
public IReadOnlyList AuthorizationDetails
{
get
@@ -41,11 +41,11 @@ public IReadOnlyList AuthorizationDetails
try
{
- _authorizationDetails = System.Text.Json.JsonSerializer.Deserialize>(AuthorizationDetailsRaw);
+ _authorizationDetails = JsonSerializer.Deserialize>(AuthorizationDetailsRaw);
_authorizationDetailsParsed = true;
return _authorizationDetails;
}
- catch (System.Text.Json.JsonException ex)
+ catch (JsonException ex)
{
throw new InvalidOperationException(
"Failed to deserialize the 'authorization_details' returned by the token endpoint.", ex);
diff --git a/src/Auth0.AuthenticationApi/Models/Ciba/LoginHint.cs b/src/Auth0.AuthenticationApi/Models/Ciba/LoginHint.cs
index 579700c01..7ad497274 100644
--- a/src/Auth0.AuthenticationApi/Models/Ciba/LoginHint.cs
+++ b/src/Auth0.AuthenticationApi/Models/Ciba/LoginHint.cs
@@ -1,4 +1,5 @@
-using Newtonsoft.Json;
+using System.Text.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models.Ciba;
@@ -7,21 +8,21 @@ namespace Auth0.AuthenticationApi.Models.Ciba;
///
public class LoginHint
{
- [JsonProperty("format")]
+ [JsonPropertyName("format")]
public string Format { get; set; }
-
+
///
- /// Issuer of the ID Token.
+ /// Issuer of the ID Token.
/// This value should match the 'Issuer' value configured in the well-known configuration.
///
- [JsonProperty("iss")]
+ [JsonPropertyName("iss")]
public string Issuer { get; set; }
-
- [JsonProperty("sub")]
+
+ [JsonPropertyName("sub")]
public string Subject { get; set; }
public override string ToString()
{
- return JsonConvert.SerializeObject(this);
+ return JsonSerializer.Serialize(this, Auth0.Core.Serialization.Auth0JsonSerializerOptions.Default);
}
-}
\ No newline at end of file
+}
diff --git a/src/Auth0.AuthenticationApi/Models/DeviceCodeResponse.cs b/src/Auth0.AuthenticationApi/Models/DeviceCodeResponse.cs
index 45fed9df5..e8e757783 100644
--- a/src/Auth0.AuthenticationApi/Models/DeviceCodeResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/DeviceCodeResponse.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -10,36 +10,36 @@ public class DeviceCodeResponse
///
/// The unique code for the device. When the user goes to the in their browser-based device, this code will be bound to their session.
///
- [JsonProperty("device_code")]
+ [JsonPropertyName("device_code")]
public string DeviceCode { get; set; }
///
/// The code that should be input at the to authorize the device.
///
- [JsonProperty("user_code")]
+ [JsonPropertyName("user_code")]
public string UserCode { get; set; }
///
/// The URL the user should visit to authorize the device.
///
- [JsonProperty("verification_uri")]
+ [JsonPropertyName("verification_uri")]
public string VerificationUri { get; set; }
///
/// The lifetime (in seconds) of the and .
///
- [JsonProperty("expires_in")]
+ [JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
///
/// The interval (in seconds) at which the app should poll the token URL to request a token.
///
- [JsonProperty("interval")]
+ [JsonPropertyName("interval")]
public int Interval { get; set; }
///
/// The complete URL the user should visit to authorize the device. This allows embedding the in the URL.
///
- [JsonProperty("verification_uri_complete")]
+ [JsonPropertyName("verification_uri_complete")]
public string VerificationUriComplete { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/Mfa/AssociateMfaAuthenticatorRequest.cs b/src/Auth0.AuthenticationApi/Models/Mfa/AssociateMfaAuthenticatorRequest.cs
index 540cfb812..97b973047 100644
--- a/src/Auth0.AuthenticationApi/Models/Mfa/AssociateMfaAuthenticatorRequest.cs
+++ b/src/Auth0.AuthenticationApi/Models/Mfa/AssociateMfaAuthenticatorRequest.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models.Mfa;
@@ -17,48 +17,48 @@ public class AssociateMfaAuthenticatorRequest
///
/// Your application's Client ID.
///
- [JsonProperty("client_id")]
+ [JsonPropertyName("client_id")]
public string ClientId { get; set; }
///
/// A JWT containing a signed assertion with your application credentials.
/// Required when Private Key JWT is your application authentication method.
///
- [JsonProperty("client_assertion")]
+ [JsonPropertyName("client_assertion")]
public string ClientAssertion { get; set; }
///
/// The value is urn:ietf:params:oauth:client-assertion-type:jwt-bearer.
/// Required when Private Key JWT is the application authentication method.
///
- [JsonProperty("client_assertion_type")]
+ [JsonPropertyName("client_assertion_type")]
public string ClientAssertionType { get; set; }
///
/// Your application's Client Secret.
/// Required when the Token Endpoint Authentication Method field in your Application Settings is Post or Basic.
///
- [JsonProperty("client_secret")]
+ [JsonPropertyName("client_secret")]
public string ClientSecret { get; set; }
///
/// The type of authenticators supported by the client.
/// Value is an array with values "otp" or "oob".
///
- [JsonProperty("authenticator_types")]
+ [JsonPropertyName("authenticator_types")]
public string[] AuthenticatorTypes { get; set; }
///
/// The type of OOB channels supported by the client.
/// Required if authenticator_types include oob.
///
- [JsonProperty("oob_channels")]
+ [JsonPropertyName("oob_channels")]
public List OobChannels { get; set; }
///
/// The phone number to use for SMS or Voice.
/// Required if oob_channels includes sms or voice.
///
- [JsonProperty("phone_number")]
+ [JsonPropertyName("phone_number")]
public string PhoneNumber { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/Mfa/AssociateMfaAuthenticatorResponse.cs b/src/Auth0.AuthenticationApi/Models/Mfa/AssociateMfaAuthenticatorResponse.cs
index 77701241e..dbab778bb 100644
--- a/src/Auth0.AuthenticationApi/Models/Mfa/AssociateMfaAuthenticatorResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/Mfa/AssociateMfaAuthenticatorResponse.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models.Mfa;
@@ -9,42 +9,42 @@ public class AssociateMfaAuthenticatorResponse
///
/// The code used for out-of-band authentication.
///
- [JsonProperty("oob_code")]
+ [JsonPropertyName("oob_code")]
public string OobCode { get; set; }
///
/// Indicates the binding method used.
///
- [JsonProperty("binding_method")]
+ [JsonPropertyName("binding_method")]
public string BindingMethod { get; set; }
///
/// Type of authenticator added.
///
- [JsonProperty("authenticator_type")]
+ [JsonPropertyName("authenticator_type")]
public string AuthenticatorType { get; set; }
///
/// The OOB channels used.
///
- [JsonProperty("oob_channel")]
+ [JsonPropertyName("oob_channel")]
public string OobChannel { get; set; }
///
/// An array of recovery codes generated for the user.
///
- [JsonProperty("recovery_codes")]
+ [JsonPropertyName("recovery_codes")]
public List RecoveryCodes { get; set; }
///
/// The URI to generate a QR code for the authenticator.
///
- [JsonProperty("barcode_uri")]
+ [JsonPropertyName("barcode_uri")]
public string BarcodeUri { get; set; }
///
/// The secret to use for the OTP.
///
- [JsonProperty("secret")]
+ [JsonPropertyName("secret")]
public string Secret { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/Mfa/MfaAuthenticator.cs b/src/Auth0.AuthenticationApi/Models/Mfa/MfaAuthenticator.cs
index 1b3639fb0..5c2d017e7 100644
--- a/src/Auth0.AuthenticationApi/Models/Mfa/MfaAuthenticator.cs
+++ b/src/Auth0.AuthenticationApi/Models/Mfa/MfaAuthenticator.cs
@@ -1,21 +1,21 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models.Mfa;
public class Authenticator
{
- [JsonProperty("id")]
+ [JsonPropertyName("id")]
public string Id { get; set; }
- [JsonProperty("authenticator_type")]
+ [JsonPropertyName("authenticator_type")]
public string AuthenticatorType { get; set; }
- [JsonProperty("oob_channel")]
+ [JsonPropertyName("oob_channel")]
public string OobChannel { get; set; }
- [JsonProperty("name")]
+ [JsonPropertyName("name")]
public string Name { get; set; }
- [JsonProperty("active")]
+ [JsonPropertyName("active")]
public bool Active { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/Mfa/MfaChallengeResponse.cs b/src/Auth0.AuthenticationApi/Models/Mfa/MfaChallengeResponse.cs
index fbae775d7..eaf1b3601 100644
--- a/src/Auth0.AuthenticationApi/Models/Mfa/MfaChallengeResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/Mfa/MfaChallengeResponse.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models.Mfa;
@@ -7,18 +7,18 @@ public class MfaChallengeResponse
///
/// Type of challenge issued.
///
- [JsonProperty("challenge_type")]
+ [JsonPropertyName("challenge_type")]
public string ChallengeType { get; set; }
///
/// Code for out-of-band challenge (only if applicable).
///
- [JsonProperty("oob_code")]
+ [JsonPropertyName("oob_code")]
public string OobCode { get; set; }
///
/// Method used for binding (only if applicable).
///
- [JsonProperty("binding_method")]
+ [JsonPropertyName("binding_method")]
public string BindingMethod { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/Mfa/MfaOobTokenResponse.cs b/src/Auth0.AuthenticationApi/Models/Mfa/MfaOobTokenResponse.cs
index 21e0a98f6..dbce89e2d 100644
--- a/src/Auth0.AuthenticationApi/Models/Mfa/MfaOobTokenResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/Mfa/MfaOobTokenResponse.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models.Mfa;
@@ -7,18 +7,18 @@ public class MfaOobTokenResponse : TokenBase
///
/// The duration in seconds that the access token is valid.
///
- [JsonProperty("expires_in")]
+ [JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
///
/// Error code returned in case of failure.
///
- [JsonProperty("error")]
+ [JsonPropertyName("error")]
public string Error { get; set; }
///
/// Description of the error.
///
- [JsonProperty("error_description")]
+ [JsonPropertyName("error_description")]
public string ErrorDescription { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/Mfa/MfaOtpTokenResponse.cs b/src/Auth0.AuthenticationApi/Models/Mfa/MfaOtpTokenResponse.cs
index 1d751523d..8a4634e4e 100644
--- a/src/Auth0.AuthenticationApi/Models/Mfa/MfaOtpTokenResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/Mfa/MfaOtpTokenResponse.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models.Mfa;
@@ -7,6 +7,6 @@ public class MfaOtpTokenResponse : TokenBase
///
/// The duration in seconds that the access token is valid.
///
- [JsonProperty("expires_in")]
+ [JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/Mfa/MfaRecoveryCodeResponse.cs b/src/Auth0.AuthenticationApi/Models/Mfa/MfaRecoveryCodeResponse.cs
index 3cec05f39..df21439f6 100644
--- a/src/Auth0.AuthenticationApi/Models/Mfa/MfaRecoveryCodeResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/Mfa/MfaRecoveryCodeResponse.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models.Mfa;
@@ -7,12 +7,12 @@ public class MfaRecoveryCodeResponse : TokenBase
///
/// The duration in seconds that the access token is valid.
///
- [JsonProperty("expires_in")]
+ [JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
///
/// Recovery code to be stored securely for future use.
///
- [JsonProperty("recovery_code")]
+ [JsonPropertyName("recovery_code")]
public string RecoveryCode { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/PasswordlessEmailRequest.cs b/src/Auth0.AuthenticationApi/Models/PasswordlessEmailRequest.cs
index 0899f8ef3..f7a3993f6 100644
--- a/src/Auth0.AuthenticationApi/Models/PasswordlessEmailRequest.cs
+++ b/src/Auth0.AuthenticationApi/Models/PasswordlessEmailRequest.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -13,13 +13,13 @@ public class PasswordlessEmailRequest
///
/// Client ID of the application.
///
- [JsonProperty("client_id")]
+ [JsonPropertyName("client_id")]
public string ClientId { get; set; }
///
/// Client Secret of the application.
///
- [JsonProperty("client_secret")]
+ [JsonPropertyName("client_secret")]
public string ClientSecret { get; set; }
///
@@ -35,18 +35,18 @@ public class PasswordlessEmailRequest
///
/// Email to which the link or code must be sent.
///
- [JsonProperty("email")]
+ [JsonPropertyName("email")]
public string Email { get; set; }
///
/// of the request.
///
- [JsonProperty("send")]
+ [JsonPropertyName("send")]
public PasswordlessEmailRequestType Type { get; set; }
///
/// Additional authentication parameters.
///
- [JsonProperty("authParams")]
+ [JsonPropertyName("authParams")]
public IDictionary AuthenticationParameters { get; set; } = new Dictionary();
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/PasswordlessEmailResponse.cs b/src/Auth0.AuthenticationApi/Models/PasswordlessEmailResponse.cs
index 3a17bd750..7bc5d456f 100644
--- a/src/Auth0.AuthenticationApi/Models/PasswordlessEmailResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/PasswordlessEmailResponse.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -10,18 +10,18 @@ public class PasswordlessEmailResponse
///
/// Identifier of this request.
///
- [JsonProperty("_id")]
+ [JsonPropertyName("_id")]
public string Id { get; set; }
///
/// Email to which the request was sent.
///
- [JsonProperty("email")]
+ [JsonPropertyName("email")]
public string Email { get; set; }
///
/// Whether the email address has been verified (true) or has not been verified (false).
///
- [JsonProperty("email_verified")]
+ [JsonPropertyName("email_verified")]
public bool? EmailVerified { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/PasswordlessSmsRequest.cs b/src/Auth0.AuthenticationApi/Models/PasswordlessSmsRequest.cs
index d21b0cb96..fcd4167cf 100644
--- a/src/Auth0.AuthenticationApi/Models/PasswordlessSmsRequest.cs
+++ b/src/Auth0.AuthenticationApi/Models/PasswordlessSmsRequest.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -10,19 +10,19 @@ public class PasswordlessSmsRequest
///
/// Client ID of the application.
///
- [JsonProperty("client_id")]
+ [JsonPropertyName("client_id")]
public string ClientId { get; set; }
///
/// Client Secret of the application.
///
- [JsonProperty("client_secret")]
+ [JsonPropertyName("client_secret")]
public string ClientSecret { get; set; }
///
/// Phone number to which the code must be sent.
///
- [JsonProperty("phone_number")]
+ [JsonPropertyName("phone_number")]
public string PhoneNumber { get; set; }
///
diff --git a/src/Auth0.AuthenticationApi/Models/PasswordlessSmsResponse.cs b/src/Auth0.AuthenticationApi/Models/PasswordlessSmsResponse.cs
index b8bc6fea0..b68178a1a 100644
--- a/src/Auth0.AuthenticationApi/Models/PasswordlessSmsResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/PasswordlessSmsResponse.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -10,18 +10,23 @@ public class PasswordlessSmsResponse
///
/// Unique identifier of the request.
///
- [JsonProperty("_id")]
+ [JsonPropertyName("_id")]
public string Id { get; set; }
///
/// Phone number to which the code was sent.
///
- [JsonProperty("phone_number")]
+ [JsonPropertyName("phone_number")]
public string PhoneNumber { get; set; }
///
/// Language the message sent was written in.
///
- [JsonProperty("request_language")]
+ ///
+ /// On deserialization this is a (previously a
+ /// Newtonsoft JToken). Read the value via the JsonElement API, e.g.
+ /// ((JsonElement)response.RequestLanguage).GetString().
+ ///
+ [JsonPropertyName("request_language")]
public object RequestLanguage { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/PushedAuthorizationRequestResponse.cs b/src/Auth0.AuthenticationApi/Models/PushedAuthorizationRequestResponse.cs
index d281e0dfe..45b568055 100644
--- a/src/Auth0.AuthenticationApi/Models/PushedAuthorizationRequestResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/PushedAuthorizationRequestResponse.cs
@@ -1,6 +1,6 @@
using System;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -15,13 +15,13 @@ public class PushedAuthorizationRequestResponse
///
/// This URI is a single-use reference to the respective request data in the subsequent authorization request.
///
- [JsonProperty("request_uri")]
+ [JsonPropertyName("request_uri")]
public Uri RequestUri { get; set; }
///
/// A number that represents the lifetime of the request URI in seconds as a positive integer.
///
- [JsonProperty("expires_in")]
+ [JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/SignupUserRequest.cs b/src/Auth0.AuthenticationApi/Models/SignupUserRequest.cs
index 1fbbb7928..4c4999f60 100644
--- a/src/Auth0.AuthenticationApi/Models/SignupUserRequest.cs
+++ b/src/Auth0.AuthenticationApi/Models/SignupUserRequest.cs
@@ -1,6 +1,6 @@
using System;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -12,54 +12,54 @@ public class SignupUserRequest : UserMaintenanceRequestBase
///
/// Initial password for this user.
///
- [JsonProperty("password")]
+ [JsonPropertyName("password")]
public string Password { get; set; }
///
/// Username of this user. Only valid if the connection requires a username.
///
- [JsonProperty("username")]
+ [JsonPropertyName("username")]
public string Username { get; set; }
///
/// Given name for this user.
///
- [JsonProperty("given_name")]
+ [JsonPropertyName("given_name")]
public string GivenName { get; set; }
///
/// Family name for this user.
///
- [JsonProperty("family_name")]
+ [JsonPropertyName("family_name")]
public string FamilyName { get; set; }
///
/// Name of this user.
///
- [JsonProperty("name")]
+ [JsonPropertyName("name")]
public string Name { get; set; }
///
/// Nickname of this user.
///
- [JsonProperty("nickname")]
+ [JsonPropertyName("nickname")]
public string Nickname { get; set; }
///
/// URL to a picture of this user.
///
- [JsonProperty("picture")]
+ [JsonPropertyName("picture")]
public Uri Picture { get; set; }
///
/// Metadata the user has read/write access to.
///
- [JsonProperty("user_metadata")]
+ [JsonPropertyName("user_metadata")]
public dynamic UserMetadata { get; set; }
///
/// The user's phone number.
///
- [JsonProperty("phone_number")]
+ [JsonPropertyName("phone_number")]
public string PhoneNumber { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/SignupUserResponse.cs b/src/Auth0.AuthenticationApi/Models/SignupUserResponse.cs
index 26713f000..48ce90c35 100644
--- a/src/Auth0.AuthenticationApi/Models/SignupUserResponse.cs
+++ b/src/Auth0.AuthenticationApi/Models/SignupUserResponse.cs
@@ -1,25 +1,27 @@
using System;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using Auth0.AuthenticationApi.Serialization;
namespace Auth0.AuthenticationApi.Models;
///
/// Represents the response from signing up a new user.
///
+[JsonConverter(typeof(SignupUserResponseConverter))]
public class SignupUserResponse
{
///
/// Email address of the new user.
///
- [JsonProperty("email")]
+ [JsonPropertyName("email")]
public string Email { get; set; }
///
/// Indicates whether the email has been verified or not.
///
/// true if the email is verified; otherwise, false.
- [JsonProperty("email_verified")]
+ [JsonPropertyName("email_verified")]
public bool EmailVerified { get; set; }
///
@@ -29,68 +31,59 @@ public class SignupUserResponse
/// The server can return `_id`, `id` or `user_id` depending on various factors.
/// For convenience we expose it here as just one.
///
- public string Id
- {
- get { return _id ?? id ?? user_id; }
- set { _id = value; }
- }
-
- [JsonProperty("_id")]
- private string _id; // Standard connection
-
-#pragma warning disable 0649
- [JsonProperty("id")]
- private string id; // Custom connection
-
- [JsonProperty("user_id")]
- private readonly string user_id; // Custom connection external
-#pragma warning restore 0649
+ [JsonIgnore]
+ public string Id { get; set; }
///
/// Username of this user.
///
- [JsonProperty("username")]
+ [JsonPropertyName("username")]
public string Username { get; set; }
///
/// Given name of this user.
///
- [JsonProperty("given_name")]
+ [JsonPropertyName("given_name")]
public string GivenName { get; set; }
///
/// Family name of this user.
///
- [JsonProperty("family_name")]
+ [JsonPropertyName("family_name")]
public string FamilyName { get; set; }
///
/// Name of this user.
///
- [JsonProperty("name")]
+ [JsonPropertyName("name")]
public string Name { get; set; }
///
/// Nickname of this user.
///
- [JsonProperty("nickname")]
+ [JsonPropertyName("nickname")]
public string Nickname { get; set; }
///
/// Url to a picture of this user.
///
- [JsonProperty("picture")]
+ [JsonPropertyName("picture")]
public Uri Picture { get; set; }
///
- /// Metadata the user has read/write access to.
+ /// Metadata the user has read/write access to.
///
- [JsonProperty("user_metadata")]
+ ///
+ /// On deserialization this is a (previously a
+ /// Newtonsoft JObject). Read values via the JsonElement API, e.g.
+ /// ((JsonElement)response.UserMetadata).GetProperty("plan").GetString().
+ ///
+ [JsonPropertyName("user_metadata")]
public dynamic UserMetadata { get; set; }
///
/// The user's phone number.
///
- [JsonProperty("phone_number")]
+ [JsonPropertyName("phone_number")]
public string PhoneNumber { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/TokenBase.cs b/src/Auth0.AuthenticationApi/Models/TokenBase.cs
index 1374c55a5..deb778fc5 100644
--- a/src/Auth0.AuthenticationApi/Models/TokenBase.cs
+++ b/src/Auth0.AuthenticationApi/Models/TokenBase.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -10,12 +10,12 @@ public abstract class TokenBase
///
/// Access token.
///
- [JsonProperty("access_token")]
+ [JsonPropertyName("access_token")]
public string AccessToken { get; set; }
///
/// Type of token.
///
- [JsonProperty("token_type")]
+ [JsonPropertyName("token_type")]
public string TokenType { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/UserInfo.cs b/src/Auth0.AuthenticationApi/Models/UserInfo.cs
index 93d68f84a..3dfcd58c1 100644
--- a/src/Auth0.AuthenticationApi/Models/UserInfo.cs
+++ b/src/Auth0.AuthenticationApi/Models/UserInfo.cs
@@ -1,7 +1,9 @@
-using System;
+using System;
using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
-using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Auth0.Core.Serialization;
@@ -16,193 +18,106 @@ namespace Auth0.AuthenticationApi.Models;
///
public class UserInfo
{
- ///
- /// Subject-Identifier for the user at the issuer. A unique value to identify the user.
- ///
- [JsonProperty("sub")]
+ /// Subject-Identifier for the user at the issuer. A unique value to identify the user.
+ [JsonPropertyName("sub")]
public string UserId { get; set; }
- ///
- /// Full name of the user in displayable form including all name parts,
- /// possibly including titles and suffixes, ordered according to the user's locale and preferences.
- ///
- [JsonProperty("name")]
+ /// Full name of the user in displayable form.
+ [JsonPropertyName("name")]
public string FullName { get; set; }
- ///
- /// Given name(s) or first name(s) of the user.
- ///
- ///
- /// May contain multiple given names separated by space characters.
- ///
- [JsonProperty("given_name")]
+ /// Given name(s) or first name(s) of the user.
+ [JsonPropertyName("given_name")]
public string FirstName { get; set; }
- ///
- /// Surname(s) or last name(s) of the user.
- ///
- ///
- /// May contain multiple given names separated by space characters or no family name at all.
- ///
- [JsonProperty("family_name")]
+ /// Surname(s) or last name(s) of the user.
+ [JsonPropertyName("family_name")]
public string LastName { get; set; }
- ///
- /// Middle name(s) of the user.
- ///
- ///
- /// May contain multiple middle names separated by space characters.
- ///
- [JsonProperty("middle_name")]
+ /// Middle name(s) of the user.
+ [JsonPropertyName("middle_name")]
public string MiddleName { get; set; }
- ///
- /// Casual name of the user that may or may not be the same as .
- ///
- ///
- /// Nickname of 'Mike' might be returned for a of `Michael`.
- ///
- [JsonProperty("nickname")]
+ /// Casual name of the user.
+ [JsonPropertyName("nickname")]
public string NickName { get; set; }
- ///
- /// Shorthand name by which the user wishes to be referred to at the RP, such as janedoe or j.doe.
- ///
- ///
- /// MAY be any valid JSON string including special characters such as @, /, or whitespace.
- /// You MUST NOT rely upon this value being unique.
- ///
- [JsonProperty("preferred_username")]
+ /// Shorthand name by which the user wishes to be referred to at the RP.
+ [JsonPropertyName("preferred_username")]
public string PreferredUsername { get; set; }
- ///
- /// URL of the user's profile page.
- ///
- ///
- /// The contents of this Web page SHOULD be about the End-User.
- ///
- [JsonProperty("profile")]
+ /// URL of the user's profile page.
+ [JsonPropertyName("profile")]
public string Profile { get; set; }
- ///
- /// URL of the user's profile picture.
- ///
- ///
- /// Note that this URL SHOULD specifically reference a profile photo of the End-User suitable for
- /// displaying when describing the user, rather than an arbitrary photo taken by the user.
- ///
- [JsonProperty("picture")]
+ /// URL of the user's profile picture.
+ [JsonPropertyName("picture")]
public string Picture { get; set; }
- ///
- /// URL of the user's Web page or blog.
- ///
- ///
- /// This Web page SHOULD contain information published by the End-User or an organization that
- /// the End-User is affiliated with.
- ///
- [JsonProperty("website")]
+ /// URL of the user's Web page or blog.
+ [JsonPropertyName("website")]
public string Website { get; set; }
- ///
- /// User's preferred e-mail address.
- ///
- ///
- /// You MUST NOT rely upon this value being unique.
- ///
- [JsonProperty("email")]
+ /// User's preferred e-mail address.
+ [JsonPropertyName("email")]
public string Email { get; set; }
- ///
- /// Whether the user's email address has been verified or not.
- ///
- ///
- /// When this Claim Value is true, this means that the OP took affirmative steps to ensure that this e-mail
- /// address was controlled by the user at the time the verification was performed. The means by which an
- /// e-mail address is verified is context-specific, and dependent upon the trust framework or contractual
- /// agreements within which the parties are operating.
- ///
- [JsonProperty("email_verified")]
+ /// Whether the user's email address has been verified or not.
+ [JsonPropertyName("email_verified")]
public bool? EmailVerified { get; set; }
- ///
- /// User's gender.
- ///
- ///
- /// Values defined by the specification include `female` and `male`.
- /// Other values MAY be used when neither of the defined values are applicable.
- ///
- [JsonProperty("gender")]
+ /// User's gender.
+ [JsonPropertyName("gender")]
public string Gender { get; set; }
- ///
- /// Gets or sets the user's birthday, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format.
- /// The year MAY be 0000, indicating that it is omitted. To represent only the year, YYYY format is allowed.
- ///
- [JsonProperty("birthdate")]
+ /// User's birthday, ISO 8601:2004 YYYY-MM-DD format.
+ [JsonPropertyName("birthdate")]
public string Birthdate { get; set; }
- ///
- /// User's time zone as a "tz database name".
- ///
- /// Europe/Paris or America/Los_Angeles.
- ///
- /// See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones for more details.
- ///
- [JsonProperty("zoneinfo")]
+ /// User's time zone as a "tz database name".
+ [JsonPropertyName("zoneinfo")]
public string ZoneInformation { get; set; }
- ///
- /// User's locale, represented as a BCP47 language tag.
- ///
- /// en-US or fr-CA
- ///
- /// Typically an ISO 639-1 Alpha-2 language code in lowercase and an
- /// ISO 3166-1 Alpha-2 country code in uppercase, separated by a dash.
- ///
- [JsonProperty("locale")]
+ /// User's locale, represented as a BCP47 language tag.
+ [JsonPropertyName("locale")]
[JsonConverter(typeof(StringOrObjectAsStringConverter))]
public string Locale { get; set; }
- ///
- /// User's preferred telephone number.
- ///
- /// +1 (425) 555-1212 or +1 (604) 555-1234;ext=5678.
- ///
- /// E.164 is the RECOMMENDED format.
- /// If the phone number contains an extension, it is RECOMMENDED that the
- /// extension be represented using the RFC 3966 extension syntax.
- ///
- [JsonProperty("phone_number")]
+ /// User's preferred telephone number.
+ [JsonPropertyName("phone_number")]
public string PhoneNumber { get; set; }
- ///
- /// Whether the user's phone number has been verified or not.
- ///
- ///
- /// When this Claim Value is true, this means that the OP took affirmative steps to ensure that this
- /// phone number was controlled by the user at the time the verification was performed.
- /// When true, the phone_number Claim MUST be in E.164 format and any extensions MUST be represented in RFC 3966 format.
- ///
- [JsonProperty("phone_number_verified")]
+ /// Whether the user's phone number has been verified or not.
+ [JsonPropertyName("phone_number_verified")]
public bool? PhoneNumberVerified { get; set; }
- ///
- /// User's preferred postal/mailing address.
- ///
- [JsonProperty("address")]
+ /// User's preferred postal/mailing address.
+ [JsonPropertyName("address")]
public UserInfoAddress Address { get; set; }
+ /// Time and date the user's information was last updated.
+ [JsonPropertyName("updated_at")]
+ [JsonConverter(typeof(Auth0.AuthenticationApi.FlexibleDateTimeConverter))]
+ public DateTime? UpdatedAt { get; set; }
+
///
- /// Time and date the user's information was last updated.
+ /// Additional claims about the user, captured as raw JSON elements.
///
- [JsonProperty("updated_at")]
- [JsonConverter(typeof(FlexibleDateTimeConverter))]
- public DateTime? UpdatedAt { get; set; }
+ [JsonExtensionData]
+ public IDictionary AdditionalClaimsJson { get; set; }
+ = new Dictionary();
///
/// Additional claims about the user.
///
- [JsonExtensionData]
- public IDictionary AdditionalClaims { get; set; }
-}
\ No newline at end of file
+ ///
+ /// Computed on each access from ; the returned dictionary is a
+ /// snapshot, so mutating it does not persist back onto the instance.
+ ///
+ [Obsolete("Use AdditionalClaimsJson. AdditionalClaims will be removed in a future major version.")]
+ [JsonIgnore]
+ public IDictionary AdditionalClaims =>
+ AdditionalClaimsJson.ToDictionary(
+ kv => kv.Key,
+ kv => JToken.Parse(kv.Value.GetRawText()));
+}
diff --git a/src/Auth0.AuthenticationApi/Models/UserInfoAddress.cs b/src/Auth0.AuthenticationApi/Models/UserInfoAddress.cs
index a2822b81e..07d84af00 100644
--- a/src/Auth0.AuthenticationApi/Models/UserInfoAddress.cs
+++ b/src/Auth0.AuthenticationApi/Models/UserInfoAddress.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -13,40 +13,40 @@ public class UserInfoAddress
///
/// MAY contain multiple lines, separated by newlines as either CRLF (Windows) `\r\n` or LF (Unix) `\n`.
///
- [JsonProperty("formatted")]
+ [JsonPropertyName("formatted")]
public string Formatted { get; set; }
///
- /// Full street address component, which MAY include house number,
+ /// Full street address component, which MAY include house number,
/// street name, Post Office Box, and multi-line extended street address information.
///
///
/// MAY contain multiple lines, separated by newlines as either CRLF (Windows) `\r\n` or LF (Unix) `\n`.
///
- [JsonProperty("street_address")]
+ [JsonPropertyName("street_address")]
public string StreetAddress { get; set; }
///
/// City or locality component of the address.
///
- [JsonProperty("locality")]
+ [JsonPropertyName("locality")]
public string Locality { get; set; }
///
/// State, province, prefecture, or region component of the address.
///
- [JsonProperty("region")]
+ [JsonPropertyName("region")]
public string Region { get; set; }
///
/// Zip or postal code component of the address.
///
- [JsonProperty("postal_code")]
+ [JsonPropertyName("postal_code")]
public string PostalCode { get; set; }
///
/// Country component of the address.
///
- [JsonProperty("country")]
+ [JsonPropertyName("country")]
public string Country { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Models/UserMaintenanceRequestBase.cs b/src/Auth0.AuthenticationApi/Models/UserMaintenanceRequestBase.cs
index dc041766f..b6494c97e 100644
--- a/src/Auth0.AuthenticationApi/Models/UserMaintenanceRequestBase.cs
+++ b/src/Auth0.AuthenticationApi/Models/UserMaintenanceRequestBase.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Auth0.AuthenticationApi.Models;
@@ -10,18 +10,18 @@ public class UserMaintenanceRequestBase
///
/// Client ID of the application.
///
- [JsonProperty("client_id")]
+ [JsonPropertyName("client_id")]
public string ClientId { get; set; }
///
/// Name of the connection.
///
- [JsonProperty("connection")]
+ [JsonPropertyName("connection")]
public string Connection { get; set; }
///
/// Email address of the user.
///
- [JsonProperty("email")]
+ [JsonPropertyName("email")]
public string Email { get; set; }
}
\ No newline at end of file
diff --git a/src/Auth0.AuthenticationApi/Serialization/SignupUserResponseConverter.cs b/src/Auth0.AuthenticationApi/Serialization/SignupUserResponseConverter.cs
new file mode 100644
index 000000000..f7ec9ba24
--- /dev/null
+++ b/src/Auth0.AuthenticationApi/Serialization/SignupUserResponseConverter.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Auth0.AuthenticationApi.Models;
+
+namespace Auth0.AuthenticationApi.Serialization;
+
+///
+/// Deserializes , resolving the user identifier from
+/// whichever of _id, id or user_id the server returned.
+///
+///
+/// System.Text.Json only reliably honours [JsonInclude] on non-public
+/// properties, not on private fields; the latter silently fail to bind on
+/// .NET Framework. This converter reads through a public-property surrogate so the mapping
+/// works identically on every target framework.
+///
+internal class SignupUserResponseConverter : JsonConverter
+{
+ // Public-property mirror of SignupUserResponse used purely as the deserialization
+ // target. It carries no converter attribute, so binding it does not recurse.
+ private class Surrogate
+ {
+ [JsonPropertyName("_id")]
+ public string UnderscoreId { get; set; }
+
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("user_id")]
+ public string UserId { get; set; }
+
+ [JsonPropertyName("email")]
+ public string Email { get; set; }
+
+ [JsonPropertyName("email_verified")]
+ public bool EmailVerified { get; set; }
+
+ [JsonPropertyName("username")]
+ public string Username { get; set; }
+
+ [JsonPropertyName("given_name")]
+ public string GivenName { get; set; }
+
+ [JsonPropertyName("family_name")]
+ public string FamilyName { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("nickname")]
+ public string Nickname { get; set; }
+
+ [JsonPropertyName("picture")]
+ public Uri Picture { get; set; }
+
+ [JsonPropertyName("user_metadata")]
+ public JsonElement? UserMetadata { get; set; }
+
+ [JsonPropertyName("phone_number")]
+ public string PhoneNumber { get; set; }
+ }
+
+ public override SignupUserResponse Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var surrogate = JsonSerializer.Deserialize(ref reader, options);
+ if (surrogate == null)
+ return null;
+
+ return new SignupUserResponse
+ {
+ Id = surrogate.UnderscoreId ?? surrogate.Id ?? surrogate.UserId,
+ Email = surrogate.Email,
+ EmailVerified = surrogate.EmailVerified,
+ Username = surrogate.Username,
+ GivenName = surrogate.GivenName,
+ FamilyName = surrogate.FamilyName,
+ Name = surrogate.Name,
+ Nickname = surrogate.Nickname,
+ Picture = surrogate.Picture,
+ UserMetadata = surrogate.UserMetadata,
+ PhoneNumber = surrogate.PhoneNumber,
+ };
+ }
+
+ public override void Write(Utf8JsonWriter writer, SignupUserResponse value, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/src/Auth0.Core/Auth0.Core.csproj b/src/Auth0.Core/Auth0.Core.csproj
index 5b471cf0d..77368b671 100644
--- a/src/Auth0.Core/Auth0.Core.csproj
+++ b/src/Auth0.Core/Auth0.Core.csproj
@@ -16,6 +16,7 @@
+
diff --git a/src/Auth0.Core/Exceptions/ApiError.cs b/src/Auth0.Core/Exceptions/ApiError.cs
index 8b344f4a1..2568349fb 100644
--- a/src/Auth0.Core/Exceptions/ApiError.cs
+++ b/src/Auth0.Core/Exceptions/ApiError.cs
@@ -1,8 +1,9 @@
-using Auth0.Core.Serialization;
-using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.Http;
+using System.Text.Json;
+using System.Text.Json.Serialization;
using System.Threading.Tasks;
+using Auth0.Core.Serialization;
namespace Auth0.Core.Exceptions;
@@ -15,25 +16,25 @@ public class ApiError
///
/// Description of the failing HTTP Status Code.
///
- [JsonProperty("error")]
+ [JsonPropertyName("error")]
public string? Error { get; set; }
///
/// Error code returned by the API.
///
- [JsonProperty("errorCode")]
+ [JsonPropertyName("errorCode")]
public string? ErrorCode { get; set; }
///
/// Description of the error.
///
- [JsonProperty("message")]
+ [JsonPropertyName("message")]
public string? Message { get; set; }
///
/// Additional key/values that might be returned by the error such as `mfa_required`.
///
- [JsonProperty("extraData")]
+ [JsonPropertyName("extraData")]
public Dictionary ExtraData { get; set; } = new();
///
@@ -54,16 +55,19 @@ public class ApiError
internal static ApiError? Parse(string? content)
{
- if (content is null)
+ if (string.IsNullOrWhiteSpace(content))
{
return null;
}
-
+
try
{
- return JsonConvert.DeserializeObject(content);
+ return JsonSerializer.Deserialize(content!, Auth0JsonSerializerOptions.Default);
}
- catch (JsonException)
+ // JsonException: malformed JSON. InvalidOperationException: valid JSON whose root is
+ // not an object (e.g. a bare string or array), which the converter cannot enumerate.
+ // Both fall back to surfacing the raw body.
+ catch (System.Exception ex) when (ex is JsonException or System.InvalidOperationException)
{
return new ApiError
{
@@ -72,4 +76,4 @@ public class ApiError
};
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Auth0.Core/Serialization/ApiErrorConverter.cs b/src/Auth0.Core/Serialization/ApiErrorConverter.cs
index 055fd49a5..6da20df80 100644
--- a/src/Auth0.Core/Serialization/ApiErrorConverter.cs
+++ b/src/Auth0.Core/Serialization/ApiErrorConverter.cs
@@ -1,59 +1,63 @@
-using System;
+using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Auth0.Core.Exceptions;
namespace Auth0.Core.Serialization;
-internal class ApiErrorConverter : JsonConverter
+internal class ApiErrorConverter : JsonConverter
{
- private readonly Dictionary _propertyMappings = new()
+ private static readonly Dictionary PropertyMappings = new(StringComparer.OrdinalIgnoreCase)
{
- {"code", "errorCode"},
- {"name", "error"},
- {"description", "message"},
- {"error_description", "message"}
+ { "code", "errorCode" },
+ { "name", "error" },
+ { "description", "message" },
+ { "error_description", "message" },
};
- public override bool CanWrite => false;
-
- public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+ public override ApiError Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- throw new NotImplementedException();
- }
+ using var doc = JsonDocument.ParseValue(ref reader);
+ var error = new ApiError();
- public override bool CanConvert(Type objectType)
- {
- return objectType.GetTypeInfo().IsClass;
- }
-
- public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
- {
- var instance = Activator.CreateInstance(objectType);
- var props = objectType.GetTypeInfo().DeclaredProperties.Where(p => p.CanWrite).ToList();
- var extraDataProp = props.FirstOrDefault(p => p.PropertyType == typeof(Dictionary));
-
- foreach (var jp in JObject.Load(reader).Properties())
+ foreach (var property in doc.RootElement.EnumerateObject())
{
- if (!_propertyMappings.TryGetValue(jp.Name, out var name))
- name = jp.Name;
-
- var prop = props.FirstOrDefault(pi => string.Equals(pi.Name, name, StringComparison.OrdinalIgnoreCase));
-
- if (prop != null)
+ if (!PropertyMappings.TryGetValue(property.Name, out var name))
+ name = property.Name;
+
+ if (string.Equals(name, "errorCode", StringComparison.OrdinalIgnoreCase))
+ error.ErrorCode = ReadString(property.Value);
+ else if (string.Equals(name, "error", StringComparison.OrdinalIgnoreCase))
+ error.Error = ReadString(property.Value);
+ else if (string.Equals(name, "message", StringComparison.OrdinalIgnoreCase))
+ error.Message = ReadString(property.Value);
+ else if (string.Equals(name, "extraData", StringComparison.OrdinalIgnoreCase)
+ && property.Value.ValueKind == JsonValueKind.Object)
{
- prop.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
+ foreach (var inner in property.Value.EnumerateObject())
+ error.ExtraData[inner.Name] = ReadString(inner.Value) ?? "";
}
- else if (extraDataProp != null && extraDataProp.PropertyType == typeof(Dictionary))
+ else
{
- var extraData = (IDictionary)extraDataProp.GetValue(instance) ?? new Dictionary();
- extraData[name] = jp.Value.ToString();
- extraDataProp.SetValue(instance, extraData);
+ error.ExtraData[name] = ReadString(property.Value) ?? "";
}
}
- return instance;
+ return error;
+ }
+
+ // Mirrors Newtonsoft's ToObject/ToString coercion: JSON null → null,
+ // strings unwrapped, other scalars kept as their raw text (never throws on non-strings).
+ private static string? ReadString(JsonElement value) => value.ValueKind switch
+ {
+ JsonValueKind.Null => null,
+ JsonValueKind.String => value.GetString(),
+ _ => value.GetRawText(),
+ };
+
+ public override void Write(Utf8JsonWriter writer, ApiError value, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
}
-}
\ No newline at end of file
+}
diff --git a/src/Auth0.Core/Serialization/Auth0JsonSerializerOptions.cs b/src/Auth0.Core/Serialization/Auth0JsonSerializerOptions.cs
new file mode 100644
index 000000000..e9c5210eb
--- /dev/null
+++ b/src/Auth0.Core/Serialization/Auth0JsonSerializerOptions.cs
@@ -0,0 +1,23 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Auth0.Core.Serialization;
+
+///
+/// Shared System.Text.Json options that reproduce the serialization behavior the SDK
+/// historically provided via Newtonsoft.Json (case-insensitive matching, null omission
+/// on write). This is the single source of truth for serialization configuration.
+///
+internal static class Auth0JsonSerializerOptions
+{
+ public static readonly JsonSerializerOptions Default = Create();
+
+ private static JsonSerializerOptions Create()
+ {
+ return new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ };
+ }
+}
diff --git a/src/Auth0.Core/Serialization/StringOrObjectAsStringConverter.cs b/src/Auth0.Core/Serialization/StringOrObjectAsStringConverter.cs
index 816c83b73..132ec9a1f 100644
--- a/src/Auth0.Core/Serialization/StringOrObjectAsStringConverter.cs
+++ b/src/Auth0.Core/Serialization/StringOrObjectAsStringConverter.cs
@@ -1,40 +1,35 @@
using System;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
+using System.IO;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
namespace Auth0.Core.Serialization;
-internal class StringOrObjectAsStringConverter : JsonConverter
+internal class StringOrObjectAsStringConverter : JsonConverter
{
- public override bool CanWrite => false;
-
- public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
- {
- throw new NotImplementedException();
- }
-
- public override bool CanConvert(Type objectType)
+ public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- return true;
- }
+ if (reader.TokenType == JsonTokenType.String)
+ return reader.GetString();
- public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
- {
- var instance = "";
-
- if (reader.TokenType == JsonToken.String)
+ if (reader.TokenType == JsonTokenType.StartObject || reader.TokenType == JsonTokenType.StartArray)
{
- instance = reader.Value?.ToString();
- }
- else if (reader.TokenType == JsonToken.StartObject)
- {
- instance = JObject.Load(reader).ToString();
- }
- else if (reader.TokenType == JsonToken.StartArray)
- {
- instance = JArray.Load(reader).ToString();
+ using var doc = JsonDocument.ParseValue(ref reader);
+ // Re-serialize to compact JSON using Utf8JsonWriter
+ using var stream = new MemoryStream();
+ using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = false }))
+ {
+ doc.RootElement.WriteTo(writer);
+ }
+ return Encoding.UTF8.GetString(stream.ToArray());
}
- return instance;
+ return "";
+ }
+
+ public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
+ {
+ throw new NotImplementedException();
}
-}
\ No newline at end of file
+}
diff --git a/src/Auth0.Core/Serialization/StringOrStringArrayJsonConverter.cs b/src/Auth0.Core/Serialization/StringOrStringArrayJsonConverter.cs
deleted file mode 100644
index 1b3ec2b3b..000000000
--- a/src/Auth0.Core/Serialization/StringOrStringArrayJsonConverter.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-namespace Auth0.Core.Serialization;
-
-internal class StringOrStringArrayJsonConverter : JsonConverter
-{
- public override bool CanWrite => false;
- public override bool CanConvert(Type objectType)
- {
- return true;
- }
-
- public override object? ReadJson(
- JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
- {
- if (reader.TokenType == JsonToken.StartArray)
- {
- // Deserialize as string array and return;
- var array = JArray.Load(reader);
- return array.ToObject();
- }
-
- if (reader.TokenType == JsonToken.String)
- {
- // Deserialize as a single string
- return reader.Value?.ToString();
- }
-
- return null;
- }
-
- public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
- {
- throw new NotImplementedException();
- }
-}
\ No newline at end of file
diff --git a/tests/Auth0.AuthenticationApi.IntegrationTests/ClientInitiatedBackchannelAuthorizationTests.cs b/tests/Auth0.AuthenticationApi.IntegrationTests/ClientInitiatedBackchannelAuthorizationTests.cs
index be1b07243..ca8defab7 100644
--- a/tests/Auth0.AuthenticationApi.IntegrationTests/ClientInitiatedBackchannelAuthorizationTests.cs
+++ b/tests/Auth0.AuthenticationApi.IntegrationTests/ClientInitiatedBackchannelAuthorizationTests.cs
@@ -3,12 +3,12 @@
using System.Net;
using System.Net.Http;
using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Moq;
using Moq.Protected;
-using Newtonsoft.Json;
using FluentAssertions;
using Auth0.AuthenticationApi.Models;
using Auth0.AuthenticationApi.Models.Ciba;
@@ -38,8 +38,8 @@ public async void Ciba_request_should_succeed()
};
var domain = GetVariable("AUTH0_AUTHENTICATION_API_URL");
- SetupMockWith(mockHandler,$"https://{domain}/bc-authorize", JsonConvert.SerializeObject(mockResponse));
- SetupMockWith(mockHandler,$"https://{domain}/oauth/token", JsonConvert.SerializeObject(mockTokenResponse));
+ SetupMockWith(mockHandler,$"https://{domain}/bc-authorize", JsonSerializer.Serialize(mockResponse, mockResponse.GetType()));
+ SetupMockWith(mockHandler,$"https://{domain}/oauth/token", JsonSerializer.Serialize(mockTokenResponse, mockTokenResponse.GetType()));
var httpClient = new HttpClient(mockHandler.Object);
var authenticationApiClient = new TestAuthenticationApiClient(domain, new TestHttpClientAuthenticationConnection(httpClient));
@@ -87,11 +87,11 @@ public async void Ciba_request_in_pending_state()
var mockTokenResponse =
"{\"error\": \"authorization_pending\",\n\"error_description\": \"The end-user authorization is pending\"\n}";
-
+
var domain = GetVariable("AUTH0_AUTHENTICATION_API_URL");
-
- SetupMockWith(mockHandler,$"https://{domain}/bc-authorize", JsonConvert.SerializeObject(mockResponse));
- SetupMockWith(mockHandler,$"https://{domain}/oauth/token", JsonConvert.SerializeObject(mockTokenResponse), HttpStatusCode.BadRequest);
+
+ SetupMockWith(mockHandler,$"https://{domain}/bc-authorize", JsonSerializer.Serialize(mockResponse, mockResponse.GetType()));
+ SetupMockWith(mockHandler,$"https://{domain}/oauth/token", mockTokenResponse, HttpStatusCode.BadRequest);
var httpClient = new HttpClient(mockHandler.Object);
var authenticationApiClient = new TestAuthenticationApiClient(domain, new TestHttpClientAuthenticationConnection(httpClient));
@@ -138,11 +138,11 @@ public async void Ciba_request_in_expired_or_rejected_state()
var mockTokenResponse =
"{\n\"error\": \"access_denied\",\n\"error_description\": \"The end-user denied the authorization request or it\nhas been expired\"\n}";
-
+
var domain = GetVariable("AUTH0_AUTHENTICATION_API_URL");
-
- SetupMockWith(mockHandler,$"https://{domain}/bc-authorize", JsonConvert.SerializeObject(mockResponse));
- SetupMockWith(mockHandler,$"https://{domain}/oauth/token", JsonConvert.SerializeObject(mockTokenResponse), HttpStatusCode.BadRequest);
+
+ SetupMockWith(mockHandler,$"https://{domain}/bc-authorize", JsonSerializer.Serialize(mockResponse, mockResponse.GetType()));
+ SetupMockWith(mockHandler,$"https://{domain}/oauth/token", mockTokenResponse, HttpStatusCode.BadRequest);
var httpClient = new HttpClient(mockHandler.Object);
var authenticationApiClient = new TestAuthenticationApiClient(domain, new TestHttpClientAuthenticationConnection(httpClient));
@@ -203,7 +203,7 @@ public async void Ciba_request_should_send_authorization_details_and_requested_e
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(mockResponse), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(mockResponse, mockResponse.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
@@ -250,7 +250,7 @@ public async void Ciba_token_response_should_expose_typed_authorization_details(
var domain = GetVariable("AUTH0_AUTHENTICATION_API_URL");
- SetupMockWith(mockHandler, $"https://{domain}/bc-authorize", JsonConvert.SerializeObject(mockResponse));
+ SetupMockWith(mockHandler, $"https://{domain}/bc-authorize", JsonSerializer.Serialize(mockResponse, mockResponse.GetType()));
SetupMockWith(mockHandler, $"https://{domain}/oauth/token", mockTokenResponse);
var httpClient = new HttpClient(mockHandler.Object);
@@ -304,7 +304,7 @@ public async void Ciba_token_response_typed_authorization_details_should_handle_
var domain = GetVariable("AUTH0_AUTHENTICATION_API_URL");
- SetupMockWith(mockHandler, $"https://{domain}/bc-authorize", JsonConvert.SerializeObject(mockResponse));
+ SetupMockWith(mockHandler, $"https://{domain}/bc-authorize", JsonSerializer.Serialize(mockResponse, mockResponse.GetType()));
SetupMockWith(mockHandler, $"https://{domain}/oauth/token", mockTokenResponse);
var httpClient = new HttpClient(mockHandler.Object);
diff --git a/tests/Auth0.AuthenticationApi.IntegrationTests/DeviceCodeTests.cs b/tests/Auth0.AuthenticationApi.IntegrationTests/DeviceCodeTests.cs
index 85e5498c4..11e9b26aa 100644
--- a/tests/Auth0.AuthenticationApi.IntegrationTests/DeviceCodeTests.cs
+++ b/tests/Auth0.AuthenticationApi.IntegrationTests/DeviceCodeTests.cs
@@ -6,13 +6,13 @@
using System.Net;
using System.Net.Http;
using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Xunit;
using Moq;
using Moq.Protected;
-using Newtonsoft.Json;
using System.Diagnostics;
namespace Auth0.AuthenticationApi.IntegrationTests;
@@ -73,7 +73,7 @@ public async Task Can_start_device_flow()
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
@@ -114,7 +114,7 @@ public async Task Can_request_token_using_device_code()
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
diff --git a/tests/Auth0.AuthenticationApi.IntegrationTests/FlexibleDateTimeConverterTests.cs b/tests/Auth0.AuthenticationApi.IntegrationTests/FlexibleDateTimeConverterTests.cs
index 26adca7f1..b8a33bde4 100644
--- a/tests/Auth0.AuthenticationApi.IntegrationTests/FlexibleDateTimeConverterTests.cs
+++ b/tests/Auth0.AuthenticationApi.IntegrationTests/FlexibleDateTimeConverterTests.cs
@@ -1,41 +1,53 @@
-using FluentAssertions;
-using Newtonsoft.Json;
using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Auth0.AuthenticationApi;
+using FluentAssertions;
using Xunit;
namespace Auth0.AuthenticationApi.IntegrationTests;
public class FlexibleDateTimeConverterTests
{
- [Fact]
- public void HandlesIsoStringDates()
+ private class TestClass
{
+ [JsonConverter(typeof(FlexibleDateTimeConverter))]
+ public DateTime? DateValue { get; set; }
+ }
- var json = "{ \"DateValue\": \"2017-01-03T19:23:03.807Z\"}";
- var result = JsonConvert.DeserializeObject(json);
+ private static TestClass Parse(string json) =>
+ JsonSerializer.Deserialize(json);
- result.DateValue.Should().Be(new DateTime(2017, 1, 3, 19, 23, 3, 807));
+ [Fact]
+ public void Handles_iso_string_with_z()
+ {
+ Parse("""{ "DateValue": "2017-01-03T19:23:03.807Z" }""")
+ .DateValue.Should().Be(new DateTime(2017, 1, 3, 19, 23, 3, 807, DateTimeKind.Utc));
}
[Fact]
- public void HandlesEpochDates()
+ public void Handles_iso_string_without_z()
{
- var json = "{ \"DateValue\": 1483650127 }";
- var result = JsonConvert.DeserializeObject(json);
- result.DateValue.Should().Be(new DateTime(2017, 1, 5, 21, 2, 7));
+ Parse("""{ "DateValue": "2017-01-03T19:23:03" }""")
+ .DateValue.Value.Year.Should().Be(2017);
}
[Fact]
- public void HandlesMissingValues()
+ public void Handles_epoch_seconds()
{
- var json = "{}";
- var result = JsonConvert.DeserializeObject(json);
- result.DateValue.Should().Be(null);
+ Parse("""{ "DateValue": 1483650127 }""")
+ .DateValue.Should().Be(new DateTime(2017, 1, 5, 21, 2, 7, DateTimeKind.Utc));
}
- internal class TestClass
+ [Fact]
+ public void Handles_missing_value()
{
- [JsonConverter(typeof(FlexibleDateTimeConverter))]
- public DateTime? DateValue { get; set; }
+ Parse("{}").DateValue.Should().BeNull();
+ }
+
+ [Fact]
+ public void Handles_null_value()
+ {
+ Parse("""{ "DateValue": null }""").DateValue.Should().BeNull();
}
-}
\ No newline at end of file
+}
diff --git a/tests/Auth0.AuthenticationApi.IntegrationTests/PasswordlessTests.cs b/tests/Auth0.AuthenticationApi.IntegrationTests/PasswordlessTests.cs
index 7e4972780..f66e41612 100644
--- a/tests/Auth0.AuthenticationApi.IntegrationTests/PasswordlessTests.cs
+++ b/tests/Auth0.AuthenticationApi.IntegrationTests/PasswordlessTests.cs
@@ -3,6 +3,7 @@
using System.Net;
using System.Net.Http;
using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
@@ -11,7 +12,6 @@
using Xunit;
using Moq;
using Moq.Protected;
-using Newtonsoft.Json;
using Auth0.AuthenticationApi.Models;
using Auth0.Tests.Shared;
@@ -191,7 +191,7 @@ public async Task Should_Request_a_token_using_sms_code()
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
@@ -247,7 +247,7 @@ public async Task Should_Request_a_token_using_email_code()
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
diff --git a/tests/Auth0.AuthenticationApi.IntegrationTests/PushedAuthorizationRequestTests.cs b/tests/Auth0.AuthenticationApi.IntegrationTests/PushedAuthorizationRequestTests.cs
index 69dd22b9c..a0cf5728c 100644
--- a/tests/Auth0.AuthenticationApi.IntegrationTests/PushedAuthorizationRequestTests.cs
+++ b/tests/Auth0.AuthenticationApi.IntegrationTests/PushedAuthorizationRequestTests.cs
@@ -5,6 +5,7 @@
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
@@ -14,7 +15,6 @@
using Microsoft.IdentityModel.Tokens;
using Moq;
using Moq.Protected;
-using Newtonsoft.Json;
using Xunit;
namespace Auth0.AuthenticationApi.IntegrationTests;
@@ -43,7 +43,7 @@ public async void Should_Call_OAuth_Par_With_Client_Secret()
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
@@ -81,7 +81,7 @@ public async void Should_Call_OAuth_Par_With_Client_Assertion()
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
@@ -171,7 +171,7 @@ public async void Should_Call_OAuth_Par_With_Arguments()
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
@@ -219,7 +219,7 @@ public async void Should_Call_OAuth_Par_With_Structured_AuthorizationDetails()
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
@@ -262,7 +262,7 @@ public async void Should_Prefer_Structured_AuthorizationDetails_Over_String()
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
@@ -306,7 +306,7 @@ public async void Should_Call_OAuth_Par_With_AuthorizationDetails_AdditionalData
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
@@ -356,7 +356,7 @@ public async void Should_Fall_Back_To_Raw_AuthorizationDetails_When_Objects_List
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
@@ -397,7 +397,7 @@ public async void Should_Call_OAuth_Par_With_AdditionalProperties()
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
- Content = new StringContent(JsonConvert.SerializeObject(response), Encoding.UTF8, "application/json"),
+ Content = new StringContent(JsonSerializer.Serialize(response, response.GetType()), Encoding.UTF8, "application/json"),
});
var httpClient = new HttpClient(mockHandler.Object);
diff --git a/tests/Auth0.AuthenticationApi.IntegrationTests/SignupUserResponseDeserializationTests.cs b/tests/Auth0.AuthenticationApi.IntegrationTests/SignupUserResponseDeserializationTests.cs
new file mode 100644
index 000000000..a119ff6b8
--- /dev/null
+++ b/tests/Auth0.AuthenticationApi.IntegrationTests/SignupUserResponseDeserializationTests.cs
@@ -0,0 +1,68 @@
+using System.Text.Json;
+using Auth0.AuthenticationApi.Models;
+using FluentAssertions;
+using Xunit;
+
+namespace Auth0.AuthenticationApi.IntegrationTests;
+
+public class SignupUserResponseDeserializationTests
+{
+ private static SignupUserResponse Parse(string json) =>
+ JsonSerializer.Deserialize(json);
+
+ [Theory]
+ [InlineData("""{ "_id": "abc123" }""")] // Standard connection
+ [InlineData("""{ "id": "abc123" }""")] // Custom connection
+ [InlineData("""{ "user_id": "abc123" }""")] // Custom connection external
+ public void Resolves_Id_from_each_server_field(string json)
+ {
+ Parse(json).Id.Should().Be("abc123");
+ }
+
+ [Fact]
+ public void Id_prefers_underscore_id_over_aliases()
+ {
+ var response = Parse("""{ "_id": "primary", "id": "secondary", "user_id": "tertiary" }""");
+
+ response.Id.Should().Be("primary");
+ }
+
+ [Fact]
+ public void Id_falls_back_to_id_when_underscore_id_absent()
+ {
+ var response = Parse("""{ "id": "secondary", "user_id": "tertiary" }""");
+
+ response.Id.Should().Be("secondary");
+ }
+
+ [Fact]
+ public void Id_is_null_when_no_identifier_present()
+ {
+ Parse("""{ "email": "user@example.com" }""").Id.Should().BeNull();
+ }
+
+ [Fact]
+ public void Maps_all_fields_through_the_converter()
+ {
+ var response = Parse("""
+ {
+ "_id": "abc123", "email": "user@example.com", "email_verified": true,
+ "username": "bsmith", "given_name": "Bob", "family_name": "Smith",
+ "name": "Bob Smith", "nickname": "Bob", "picture": "https://example.com/p.png",
+ "phone_number": "+15551234", "user_metadata": { "plan": "gold" }
+ }
+ """);
+
+ response.Id.Should().Be("abc123");
+ response.Email.Should().Be("user@example.com");
+ response.EmailVerified.Should().BeTrue();
+ response.Username.Should().Be("bsmith");
+ response.GivenName.Should().Be("Bob");
+ response.FamilyName.Should().Be("Smith");
+ response.Name.Should().Be("Bob Smith");
+ response.Nickname.Should().Be("Bob");
+ response.Picture.Should().Be(new System.Uri("https://example.com/p.png"));
+ response.PhoneNumber.Should().Be("+15551234");
+ ((string)response.UserMetadata.GetProperty("plan").GetString()).Should().Be("gold");
+ }
+}
diff --git a/tests/Auth0.AuthenticationApi.IntegrationTests/UserInfoDeserializationTests.cs b/tests/Auth0.AuthenticationApi.IntegrationTests/UserInfoDeserializationTests.cs
index c1d7a8c34..e91e8cd1c 100644
--- a/tests/Auth0.AuthenticationApi.IntegrationTests/UserInfoDeserializationTests.cs
+++ b/tests/Auth0.AuthenticationApi.IntegrationTests/UserInfoDeserializationTests.cs
@@ -1,48 +1,35 @@
-using Auth0.AuthenticationApi.Models;
+using System;
+using System.Text.Json;
+using Auth0.AuthenticationApi.Models;
using FluentAssertions;
-using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using System;
using Xunit;
namespace Auth0.AuthenticationApi.IntegrationTests;
public class UserInfoDeserializationTests
{
+ private static UserInfo Parse(string json) =>
+ JsonSerializer.Deserialize(json);
[Fact]
public void Can_read_standard_claims()
{
- var jsonPayload = @"{
- 'sub': '123456',
- 'name': 'Robert Smith',
- 'given_name': 'Robert',
- 'family_name': 'Smith',
- 'middle_name': 'Franklin',
- 'nickname': 'Bob',
- 'preferred_username': 'b.smith',
- 'profile': 'http://profiles.com/bsmith',
- 'picture': 'http://mycompany.com/bob_photo.jpg',
- 'website': 'http://mycompany.com/users/bob',
- 'email': 'bob@mycompany.com',
- 'email_verified': true,
- 'gender': 'male',
- 'birthdate': '0000-05-22',
- 'zoneinfo': 'Europe/Paris',
- 'locale': 'en-US',
- 'phone_number': '+1 (604) 555-1234;ext5678',
- 'phone_number_verified': false,
- 'address': {
- 'formatted' : '123 Main St., Anytown, TX 77777',
- 'street_address': '123 Main St.',
- 'locality': 'Anytown',
- 'region': 'Texas',
- 'postal_code': '77777',
- 'country': 'US'
- },
- 'updated_at': 1233233333
-}";
- var userInfo = GetUserInfoFromJsonPayload(jsonPayload);
+ var json = """
+ {
+ "sub": "123456", "name": "Robert Smith", "given_name": "Robert",
+ "family_name": "Smith", "middle_name": "Franklin", "nickname": "Bob",
+ "preferred_username": "b.smith", "profile": "http://profiles.com/bsmith",
+ "picture": "http://mycompany.com/bob_photo.jpg", "website": "http://mycompany.com/users/bob",
+ "email": "bob@mycompany.com", "email_verified": true, "gender": "male",
+ "birthdate": "0000-05-22", "zoneinfo": "Europe/Paris", "locale": "en-US",
+ "phone_number": "+1 (604) 555-1234;ext5678", "phone_number_verified": false,
+ "address": { "formatted": "123 Main St., Anytown, TX 77777", "street_address": "123 Main St.",
+ "locality": "Anytown", "region": "Texas", "postal_code": "77777", "country": "US" },
+ "updated_at": 1233233333
+ }
+ """;
+ var userInfo = Parse(json);
userInfo.UserId.Should().Be("123456");
userInfo.FullName.Should().Be("Robert Smith");
@@ -51,81 +38,121 @@ public void Can_read_standard_claims()
userInfo.MiddleName.Should().Be("Franklin");
userInfo.NickName.Should().Be("Bob");
userInfo.PreferredUsername.Should().Be("b.smith");
- userInfo.Profile.Should().Be("http://profiles.com/bsmith");
- userInfo.Picture.Should().Be("http://mycompany.com/bob_photo.jpg");
- userInfo.Website.Should().Be("http://mycompany.com/users/bob");
userInfo.Email.Should().Be("bob@mycompany.com");
userInfo.EmailVerified.Should().Be(true);
- userInfo.Gender.Should().Be("male");
userInfo.Birthdate.Should().Be("0000-05-22");
- userInfo.ZoneInformation.Should().Be("Europe/Paris");
userInfo.Locale.Should().Be("en-US");
- userInfo.PhoneNumber.Should().Be("+1 (604) 555-1234;ext5678");
userInfo.PhoneNumberVerified.Should().Be(false);
- userInfo.Address.Formatted.Should().Be("123 Main St., Anytown, TX 77777");
- userInfo.Address.StreetAddress.Should().Be("123 Main St.");
- userInfo.Address.Locality.Should().Be("Anytown");
- userInfo.Address.Region.Should().Be("Texas");
- userInfo.Address.PostalCode.Should().Be("77777");
userInfo.Address.Country.Should().Be("US");
- userInfo.UpdatedAt.Should().Be(new DateTime(2009, 1, 29, 12, 48, 53));
+ userInfo.UpdatedAt.Should().Be(new DateTime(2009, 1, 29, 12, 48, 53, DateTimeKind.Utc));
}
[Fact]
- public void Can_read_custom_locale_claim()
+ public void Can_read_custom_locale_claim_as_compact_string()
{
- var jsonPayload = @"{
- 'sub': '123456',
- 'locale': {
- 'country': 'US',
- 'language': 'en'
- },
- 'updated_at': 1233233333
-}";
- var userInfo = GetUserInfoFromJsonPayload(jsonPayload);
+ var userInfo = Parse("""{ "sub": "123456", "locale": { "country": "US", "language": "en" } }""");
- userInfo.UserId.Should().Be("123456");
userInfo.Locale.Should().NotBeNull();
- JObject.Parse(userInfo.Locale).GetValue("country")!.Value().Should().Be("US");
- JObject.Parse(userInfo.Locale).GetValue("language")!.Value().Should().Be("en");
+ var locale = JsonDocument.Parse(userInfo.Locale).RootElement;
+ locale.GetProperty("country").GetString().Should().Be("US");
+ locale.GetProperty("language").GetString().Should().Be("en");
}
-
+
[Fact]
public void Missing_values_are_null()
{
- var jsonPayload = @"{ 'sub': '123456'}";
- var userInfo = GetUserInfoFromJsonPayload(jsonPayload);
- Assert.Null(userInfo.FullName);
- Assert.Null(userInfo.EmailVerified);
- Assert.Null(userInfo.PhoneNumberVerified);
- Assert.Null(userInfo.Address);
- Assert.Null(userInfo.UpdatedAt);
+ var userInfo = Parse("""{ "sub": "123456" }""");
+
+ userInfo.FullName.Should().BeNull();
+ userInfo.EmailVerified.Should().BeNull();
+ userInfo.PhoneNumberVerified.Should().BeNull();
+ userInfo.Address.Should().BeNull();
+ userInfo.UpdatedAt.Should().BeNull();
}
[Fact]
- public void Can_read_additional_claims()
+ public void UpdatedAt_reads_iso8601_string()
{
- var jsonPayload = @"{
- 'sub': '123456',
- 'http://acme.com/claims/groupIds': [ 'bobsdepartment','administrators' ],
- 'http://acme.com/claims/manager': { 'name' : 'John Doe' },
- 'http://acme.com/claims/office': 'building 125'
-}";
- var userInfo = GetUserInfoFromJsonPayload(jsonPayload);
+ var userInfo = Parse("""{ "sub": "123456", "updated_at": "2009-01-29T12:48:53Z" }""");
- var groups = (JArray)userInfo.AdditionalClaims["http://acme.com/claims/groupIds"];
+ userInfo.UpdatedAt.Should().Be(new DateTime(2009, 1, 29, 12, 48, 53, DateTimeKind.Utc));
+ }
+
+ [Fact]
+ public void UpdatedAt_falls_back_to_null_for_unparseable_string()
+ {
+ // A non-ISO / malformed updated_at must not fail the whole UserInfo deserialization.
+ var userInfo = Parse("""{ "sub": "123456", "email": "bob@mycompany.com", "updated_at": "not-a-date" }""");
+
+ userInfo.UserId.Should().Be("123456");
+ userInfo.Email.Should().Be("bob@mycompany.com");
+ userInfo.UpdatedAt.Should().BeNull();
+ }
+
+ [Fact]
+ public void Can_read_additional_claims_via_new_json_property()
+ {
+ var userInfo = Parse("""
+ {
+ "sub": "123456",
+ "http://acme.com/claims/groupIds": [ "bobsdepartment", "administrators" ],
+ "http://acme.com/claims/manager": { "name": "John Doe" }
+ }
+ """);
- Assert.Equal(2, groups.Count);
- Assert.Equal("bobsdepartment", (string)groups[0]);
- Assert.Equal("administrators", (string)groups[1]);
+ var groups = userInfo.AdditionalClaimsJson["http://acme.com/claims/groupIds"];
+ groups.ValueKind.Should().Be(JsonValueKind.Array);
+ groups[0].GetString().Should().Be("bobsdepartment");
+ groups[1].GetString().Should().Be("administrators");
+
+ userInfo.AdditionalClaimsJson["http://acme.com/claims/manager"]
+ .GetProperty("name").GetString().Should().Be("John Doe");
+ }
+
+ [Fact]
+ public void Obsolete_AdditionalClaims_shim_still_returns_jtokens()
+ {
+#pragma warning disable CS0618 // testing the obsolete member on purpose
+ var userInfo = Parse("""
+ {
+ "sub": "123456",
+ "http://acme.com/claims/groupIds": [ "bobsdepartment", "administrators" ],
+ "http://acme.com/claims/manager": { "name": "John Doe" }
+ }
+ """);
+
+ var groups = (JArray)userInfo.AdditionalClaims["http://acme.com/claims/groupIds"];
+ groups.Count.Should().Be(2);
+ ((string)groups[0]).Should().Be("bobsdepartment");
dynamic manager = userInfo.AdditionalClaims["http://acme.com/claims/manager"];
string managerName = manager.name;
managerName.Should().Be("John Doe");
+#pragma warning restore CS0618
+ }
+
+ [Fact]
+ public void Scalar_additional_claim_is_captured()
+ {
+ var userInfo = Parse("""{ "sub": "123456", "login_count": 42 }""");
+
+ userInfo.AdditionalClaimsJson["login_count"].GetInt32().Should().Be(42);
+
+#pragma warning disable CS0618 // testing the obsolete member on purpose
+ ((int)userInfo.AdditionalClaims["login_count"]).Should().Be(42);
+#pragma warning restore CS0618
}
- private UserInfo GetUserInfoFromJsonPayload(string jsonPayload)
+ [Fact]
+ public void Null_additional_claim_is_captured()
{
- return JsonConvert.DeserializeObject(jsonPayload);
+ var userInfo = Parse("""{ "sub": "123456", "optional_claim": null }""");
+
+ userInfo.AdditionalClaimsJson.Should().ContainKey("optional_claim");
+ userInfo.AdditionalClaimsJson["optional_claim"].ValueKind.Should().Be(JsonValueKind.Null);
+
+#pragma warning disable CS0618 // testing the obsolete member on purpose
+ userInfo.AdditionalClaims["optional_claim"].Type.Should().Be(JTokenType.Null);
+#pragma warning restore CS0618
}
-}
\ No newline at end of file
+}
diff --git a/tests/Auth0.Core.UnitTests/ApiErrorDeserializationTests.cs b/tests/Auth0.Core.UnitTests/ApiErrorDeserializationTests.cs
index 3749dce89..335c0cf74 100644
--- a/tests/Auth0.Core.UnitTests/ApiErrorDeserializationTests.cs
+++ b/tests/Auth0.Core.UnitTests/ApiErrorDeserializationTests.cs
@@ -1,12 +1,6 @@
-using System;
-using System.Collections;
+using Auth0.Core.Exceptions;
using FluentAssertions;
-using Newtonsoft.Json;
using Xunit;
-using System.Collections.Generic;
-using System.IO;
-using Auth0.Core.Exceptions;
-using Auth0.Core.Serialization;
namespace Auth0.Core.UnitTests;
@@ -16,229 +10,87 @@ public class ApiErrorDeserializationTests
public void Should_deserialize_extra_properties_like_mfa_response()
{
var content =
- "{\"error\": \"mfa_required\", \"error_description\": \"Multifactor authentication required\", \"mfa_token\": \"2x4b-r2d2-c3po-bb8-ig88\"}";
+ """{"error": "mfa_required", "error_description": "Multifactor authentication required", "mfa_token": "2x4b-r2d2-c3po-bb8-ig88"}""";
var parsed = ApiError.Parse(content);
- Assert.Equal("mfa_required", parsed.Error);
- Assert.Equal("Multifactor authentication required", parsed.Message);
- Assert.Contains(parsed.ExtraData,
- (d) => d.Key == "mfa_token" && string.Equals(d.Value, "2x4b-r2d2-c3po-bb8-ig88"));
- }
-
- [Theory]
- [ClassData(typeof(ApiErrorDeserializationData))]
- public void Should_deserialize_all_error_structures_correctly(string content, ApiError expected)
- {
- var error = JsonConvert.DeserializeObject(content);
-
- error.Should().BeEquivalentTo(expected);
+ parsed.Error.Should().Be("mfa_required");
+ parsed.Message.Should().Be("Multifactor authentication required");
+ parsed.ExtraData.Should().ContainKey("mfa_token")
+ .WhichValue.Should().Be("2x4b-r2d2-c3po-bb8-ig88");
}
[Fact]
- public void CanConvert_WithClassType_ReturnsTrue()
+ public void Maps_camelcase_errorCode_alias_to_error_code()
{
- var converter = new ApiErrorConverter();
- var result = converter.CanConvert(typeof(string));
- result.Should().BeTrue();
- }
+ var parsed = ApiError.Parse(
+ """{"error": "Error", "message": "The Message", "errorCode": "error_code"}""");
- [Fact]
- public void CanConvert_WithValueType_ReturnsFalse()
- {
- var converter = new ApiErrorConverter();
- var result = converter.CanConvert(typeof(int));
- result.Should().BeFalse();
+ parsed.Error.Should().Be("Error");
+ parsed.Message.Should().Be("The Message");
+ parsed.ErrorCode.Should().Be("error_code");
}
[Fact]
- public void CanWrite_ReturnsFalse()
+ public void Maps_aliased_names_to_canonical_properties()
{
- var converter = new ApiErrorConverter();
- converter.CanWrite.Should().BeFalse();
- }
+ var parsed = ApiError.Parse(
+ """{"code": "test_code", "name": "test_error", "description": "test_message"}""");
- [Fact]
- public void WriteJson_ThrowsNotImplementedException()
- {
- var converter = new ApiErrorConverter();
- var writer = new JsonTextWriter(new StringWriter());
-
- Action act = () => converter.WriteJson(writer, new object(), new JsonSerializer());
-
- act.Should().Throw();
+ parsed.ErrorCode.Should().Be("test_code");
+ parsed.Error.Should().Be("test_error");
+ parsed.Message.Should().Be("test_message");
}
[Fact]
- public void ReadJson_WithMappedPropertyNames_MapsToCorrectProperties()
+ public void Maps_error_description_to_message()
{
- var converter = new ApiErrorConverter();
- var json = """{"code": "test_code", "name": "test_error", "description": "test_message"}""";
- var reader = new JsonTextReader(new StringReader(json));
- var serializer = new JsonSerializer();
+ var parsed = ApiError.Parse("""{"error_description": "test_description"}""");
- var result = converter.ReadJson(reader, typeof(TestApiError), null, serializer) as TestApiError;
-
- result.Should().NotBeNull();
- result.ErrorCode.Should().Be("test_code");
- result.Error.Should().Be("test_error");
- result.Message.Should().Be("test_message");
+ parsed.Message.Should().Be("test_description");
}
[Fact]
- public void ReadJson_WithErrorDescriptionProperty_MapsToMessage()
+ public void Matches_keys_case_insensitively()
{
- var converter = new ApiErrorConverter();
- var json = """{"error_description": "test_description"}""";
- var reader = new JsonTextReader(new StringReader(json));
- var serializer = new JsonSerializer();
-
- var result = converter.ReadJson(reader, typeof(TestApiError), null, serializer) as TestApiError;
+ var parsed = ApiError.Parse("""{"ERRORCODE": "test_code", "ERROR": "test_error"}""");
- result.Should().NotBeNull();
- result.Message.Should().Be("test_description");
+ parsed.ErrorCode.Should().Be("test_code");
+ parsed.Error.Should().Be("test_error");
}
[Fact]
- public void ReadJson_WithUnmappedProperties_SetsPropertiesDirectly()
+ public void Empty_json_yields_all_null_fields()
{
- var converter = new ApiErrorConverter();
- var json = """{"customProperty": "custom_value"}""";
- var reader = new JsonTextReader(new StringReader(json));
- var serializer = new JsonSerializer();
+ var parsed = ApiError.Parse("{}");
- var result =
- converter.ReadJson(reader, typeof(TestApiErrorWithCustomProperty), null, serializer) as
- TestApiErrorWithCustomProperty;
-
- result.Should().NotBeNull();
- result.CustomProperty.Should().Be("custom_value");
- }
-
- [Fact]
- public void ReadJson_WithUnmappedPropertiesAndExtraDataDictionary_AddsToExtraData()
- {
- var converter = new ApiErrorConverter();
- var json = """{"unmappedProperty": "unmapped_value"}""";
- var reader = new JsonTextReader(new StringReader(json));
- var serializer = new JsonSerializer();
-
- var result =
- converter.ReadJson(reader, typeof(TestApiErrorWithExtraData), null,
- serializer) as TestApiErrorWithExtraData;
-
- result.Should().NotBeNull();
- result.ExtraData.Should().ContainKey("unmappedProperty");
- result.ExtraData["unmappedProperty"].Should().Be("unmapped_value");
+ parsed.ErrorCode.Should().BeNull();
+ parsed.Error.Should().BeNull();
+ parsed.Message.Should().BeNull();
+ parsed.ExtraData.Should().BeEmpty();
}
[Fact]
- public void ReadJson_WithCaseInsensitivePropertyNames_MapsCorrectly()
+ public void Null_mapped_field_stays_null()
{
- var converter = new ApiErrorConverter();
- var json = """{"ERRORCODE": "test_code", "ERROR": "test_error"}""";
- var reader = new JsonTextReader(new StringReader(json));
- var serializer = new JsonSerializer();
+ var parsed = ApiError.Parse("""{"code": null}""");
- var result = converter.ReadJson(reader, typeof(TestApiError), null, serializer) as TestApiError;
-
- result.Should().NotBeNull();
- result.ErrorCode.Should().Be("test_code");
- result.Error.Should().Be("test_error");
+ parsed.ErrorCode.Should().BeNull();
}
[Fact]
- public void ReadJson_WithEmptyJson_ReturnsInstanceWithDefaultValues()
+ public void Non_string_mapped_field_kept_as_raw_text()
{
- var converter = new ApiErrorConverter();
- var json = """{}""";
- var reader = new JsonTextReader(new StringReader(json));
- var serializer = new JsonSerializer();
-
- var result = converter.ReadJson(reader, typeof(TestApiError), null, serializer) as TestApiError;
+ var parsed = ApiError.Parse("""{"code": 123}""");
- result.Should().NotBeNull();
- result.ErrorCode.Should().BeNull();
- result.Error.Should().BeNull();
- result.Message.Should().BeNull();
+ parsed.ErrorCode.Should().Be("123");
}
[Fact]
- public void ReadJson_WithReadOnlyProperties_IgnoresReadOnlyProperties()
- {
- var converter = new ApiErrorConverter();
- var json = """{"readOnlyProperty": "should_be_ignored", "errorCode": "test_code"}""";
- var reader = new JsonTextReader(new StringReader(json));
- var serializer = new JsonSerializer();
-
- var result =
- converter.ReadJson(reader, typeof(TestApiErrorWithReadOnlyProperty), null, serializer) as
- TestApiErrorWithReadOnlyProperty;
-
- result.Should().NotBeNull();
- result.ErrorCode.Should().Be("test_code");
- result.ReadOnlyProperty.Should().Be("default_value");
- }
-}
-
-public class ApiErrorDeserializationData : IEnumerable