diff --git a/CHANGELOG.md b/CHANGELOG.md index eba1076..1bce47a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## [v0.11.0-beta.1](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.11.0-beta.1) +### 🚀 Features +- **System.Text.Json Migration Support (Phase 1)** + - Added `SerializationMode` enum with support for `Newtonsoft`, `SystemTextJson`, and `Auto` modes + - Implemented serialization abstraction layer with `ISerializationEngine` interface + - Added `NewtonsoftSerializationEngine` and `SystemTextJsonSerializationEngine` implementations + - Introduced STJ-specific response methods: `OpenJsonObjectResponse()` and `OpenTResponseStj()` + - Added dual JSON attribute support: existing `[JsonProperty]` + new `[JsonPropertyName]` + - Implemented comprehensive STJ JSON navigation and deserialization patterns +### 🔧 Technical Improvements +- **Enhanced JSON Handling** + - Added `JsonConversionExtensions` for seamless JObject ↔ JsonObject conversion + - Implemented `DualJsonWriter` with `Utf8JsonWriterInfo` helper for dynamic JSON operations + - Fixed ambiguous `JsonSerializer` references throughout codebase + - Enhanced `IResponse` interface with STJ-specific methods +### 🏗️ Architecture Changes +- **Serialization Engine Factory** + - Added `SerializationEngineFactory` for dynamic serialization engine selection + - Integrated serialization mode detection across all service layers + - Updated all model constructors to accept both Newtonsoft and STJ serializers +### 📦 Dependencies +- Added `System.Text.Json` NuGet package dependency +- Maintained backward compatibility with existing `Newtonsoft.Json` usage +### ⚠️ Breaking Changes +- **SerializationMode Selection**: STJ usage requires explicit `SerializationMode.SystemTextJson` configuration +- **Constructor Changes**: Service classes now require serializer parameter in constructors +- **Response Method Additions**: New STJ-specific methods added to `IResponse` interface +### 🧪 Migration Notes +- **Backward Compatible**: Existing Newtonsoft.Json code continues to work unchanged +- **Incremental Adoption**: Teams can migrate module-by-module using `SerializationMode` +- **Testing Required**: Validate serialization behavior when switching modes + ## [v0.10.0](https://github.com/contentstack/contentstack-management-dotnet/tree/v0.9.0) - Feat diff --git a/Contentstack.Management.Core.Unit.Tests/Mokes/MockHttpResponse.cs b/Contentstack.Management.Core.Unit.Tests/Mokes/MockHttpResponse.cs index 044de05..ac087e9 100644 --- a/Contentstack.Management.Core.Unit.Tests/Mokes/MockHttpResponse.cs +++ b/Contentstack.Management.Core.Unit.Tests/Mokes/MockHttpResponse.cs @@ -3,6 +3,7 @@ using System.Net; using Contentstack.Management.Core; using Newtonsoft.Json.Linq; +using System.Text.Json.Nodes; namespace Contentstack.Management.Core.Unit.Tests.Mokes { @@ -85,5 +86,36 @@ public TResponse OpenTResponse() return default(TResponse); } } + + public JsonObject OpenJsonObjectResponse() + { + if (string.IsNullOrEmpty(_responseContent)) + return new JsonObject(); + + try + { + return JsonNode.Parse(_responseContent)?.AsObject() ?? new JsonObject(); + } + catch + { + // Return empty JsonObject if parsing fails + return new JsonObject(); + } + } + + public TResponse OpenTResponseStj() + { + if (string.IsNullOrEmpty(_responseContent)) + return default(TResponse); + + try + { + return System.Text.Json.JsonSerializer.Deserialize(_responseContent); + } + catch + { + return default(TResponse); + } + } } } \ No newline at end of file diff --git a/Contentstack.Management.Core/ContentstackClient.cs b/Contentstack.Management.Core/ContentstackClient.cs index a6b16cd..f9a9236 100644 --- a/Contentstack.Management.Core/ContentstackClient.cs +++ b/Contentstack.Management.Core/ContentstackClient.cs @@ -19,6 +19,9 @@ using Environment = System.Environment; using System.Collections.Generic; using Contentstack.Management.Core.Runtime.Pipeline.RetryHandler; +using System.Text.Json; +using Contentstack.Management.Core.Enums; +using Contentstack.Management.Core.Services.Serialization; namespace Contentstack.Management.Core { @@ -29,7 +32,9 @@ public class ContentstackClient : IContentstackClient { internal ContentstackRuntimePipeline ContentstackPipeline { get; set; } internal ContentstackClientOptions contentstackOptions; - internal JsonSerializer serializer => JsonSerializer.Create(SerializerSettings); + internal Newtonsoft.Json.JsonSerializer serializer => Newtonsoft.Json.JsonSerializer.Create(SerializerSettings); + internal ISerializationEngine SerializationEngine => SerializationEngineFactory.Create( + SerializationMode, SerializerSettings, SerializerOptions); #region Private private HttpClient _httpClient; @@ -53,6 +58,16 @@ public class ContentstackClient : IContentstackClient /// public JsonSerializerSettings SerializerSettings { get; set; } = new JsonSerializerSettings(); + /// + /// Get and Set method for System.Text.Json serialization options. + /// + public JsonSerializerOptions SerializerOptions { get; set; } = new JsonSerializerOptions(); + + /// + /// Get and Set method for controlling which serialization engine to use. + /// + public SerializationMode SerializationMode { get; set; } = SerializationMode.Newtonsoft; + #endregion #region Constructor @@ -372,7 +387,7 @@ public Stack Stack(string apiKey = null, string managementToken = null, string b public ContentstackResponse Login(ICredentials credentials, string token = null, string mfaSecret = null) { ThrowIfAlreadyLoggedIn(); - LoginService Login = new LoginService(serializer, credentials, token, mfaSecret); + LoginService Login = new LoginService(serializer, credentials, token, mfaSecret, SerializerOptions, SerializationMode); return InvokeSync(Login); } @@ -394,7 +409,7 @@ public ContentstackResponse Login(ICredentials credentials, string token = null, public Task LoginAsync(ICredentials credentials, string token = null, string mfaSecret = null) { ThrowIfAlreadyLoggedIn(); - LoginService Login = new LoginService(serializer, credentials, token, mfaSecret); + LoginService Login = new LoginService(serializer, credentials, token, mfaSecret, SerializerOptions, SerializationMode); return InvokeAsync(Login); } @@ -434,7 +449,7 @@ internal void ThrowIfNotLoggedIn() public ContentstackResponse Logout(string authtoken = null) { string token = authtoken ?? contentstackOptions.Authtoken; - LogoutService logout = new LogoutService(serializer, token); + LogoutService logout = new LogoutService(serializer, token, SerializerOptions, SerializationMode); return InvokeSync(logout); } @@ -452,7 +467,7 @@ public ContentstackResponse Logout(string authtoken = null) public Task LogoutAsync(string authtoken = null) { string token = authtoken ?? contentstackOptions.Authtoken; - LogoutService logout = new LogoutService(serializer, token); + LogoutService logout = new LogoutService(serializer, token, SerializerOptions, SerializationMode); return InvokeAsync(logout); } @@ -677,7 +692,7 @@ public ContentstackResponse GetUser(ParameterCollection collection = null) { ThrowIfNotLoggedIn(); - GetLoggedInUserService getUser = new GetLoggedInUserService(serializer, collection); + GetLoggedInUserService getUser = new GetLoggedInUserService(serializer, collection, SerializerOptions, SerializationMode); return InvokeSync(getUser); } @@ -696,7 +711,7 @@ public Task GetUserAsync(ParameterCollection collection = { ThrowIfNotLoggedIn(); - GetLoggedInUserService getUser = new GetLoggedInUserService(serializer, collection); + GetLoggedInUserService getUser = new GetLoggedInUserService(serializer, collection, SerializerOptions, SerializationMode); return InvokeAsync(getUser); } diff --git a/Contentstack.Management.Core/ContentstackResponse.cs b/Contentstack.Management.Core/ContentstackResponse.cs index 0423f2a..74d962e 100644 --- a/Contentstack.Management.Core/ContentstackResponse.cs +++ b/Contentstack.Management.Core/ContentstackResponse.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -6,6 +6,8 @@ using System.Net.Http.Headers; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; namespace Contentstack.Management.Core { @@ -20,7 +22,8 @@ public class ContentstackResponse : IResponse, IDisposable Dictionary _headers; HashSet _headerNamesSet; private readonly HttpResponseMessage _response; - private readonly JsonSerializer _serializer; + private readonly Newtonsoft.Json.JsonSerializer _serializer; + private readonly JsonSerializerOptions _stjOptions; #region Public /// @@ -126,10 +129,11 @@ private void CopyHeaderValues(HttpResponseMessage response) } #endregion - internal ContentstackResponse(HttpResponseMessage response, JsonSerializer serializer) + internal ContentstackResponse(HttpResponseMessage response, Newtonsoft.Json.JsonSerializer serializer, JsonSerializerOptions stjOptions = null) { _response = response; _serializer = serializer; + _stjOptions = stjOptions ?? new JsonSerializerOptions(); this.StatusCode = response.StatusCode; this.IsSuccessStatusCode = response.IsSuccessStatusCode; @@ -175,6 +179,28 @@ public TResponse OpenTResponse() return jObject.ToObject(_serializer); } + /// + /// System.Text.Json JsonObject format response. + /// + /// The JsonObject. + public JsonObject OpenJsonObjectResponse() + { + ThrowIfDisposed(); + return JsonNode.Parse(OpenResponse())?.AsObject(); + } + + /// + /// Type response to serialize the response using System.Text.Json. + /// + /// The type to serialize the response into. + /// + public TResponse OpenTResponseStj() + { + ThrowIfDisposed(); + string json = OpenResponse(); + return System.Text.Json.JsonSerializer.Deserialize(json, _stjOptions); + } + #region Dispose method public void Dispose() diff --git a/Contentstack.Management.Core/Enums/SerializationMode.cs b/Contentstack.Management.Core/Enums/SerializationMode.cs new file mode 100644 index 0000000..b18c9d9 --- /dev/null +++ b/Contentstack.Management.Core/Enums/SerializationMode.cs @@ -0,0 +1,23 @@ +namespace Contentstack.Management.Core.Enums +{ + /// + /// Defines the serialization mode for JSON operations. + /// + public enum SerializationMode + { + /// + /// Use Newtonsoft.Json for serialization (default for backward compatibility). + /// + Newtonsoft = 0, + + /// + /// Use System.Text.Json for serialization. + /// + SystemTextJson = 1, + + /// + /// Auto-detect best serialization mode based on context (future implementation). + /// + Auto = 2 + } +} \ No newline at end of file diff --git a/Contentstack.Management.Core/Exceptions/ContentstackErrorException.cs b/Contentstack.Management.Core/Exceptions/ContentstackErrorException.cs index 2f35ba6..1626b02 100644 --- a/Contentstack.Management.Core/Exceptions/ContentstackErrorException.cs +++ b/Contentstack.Management.Core/Exceptions/ContentstackErrorException.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Exceptions { @@ -42,6 +43,7 @@ public class ContentstackErrorException: Exception /// This is error message. /// [JsonProperty("error_message")] + [JsonPropertyName("error_message")] public string ErrorMessage { get @@ -59,12 +61,14 @@ public string ErrorMessage /// This is error code. /// [JsonProperty("error_code")] + [JsonPropertyName("error_code")] public int ErrorCode { get; set; } /// /// Set of errors in detail. /// [JsonProperty("errors")] + [JsonPropertyName("errors")] public Dictionary Errors { get; set; } /// diff --git a/Contentstack.Management.Core/Http/ContentstackHttpRequest.cs b/Contentstack.Management.Core/Http/ContentstackHttpRequest.cs index fb00fa0..4f2b3e9 100644 --- a/Contentstack.Management.Core/Http/ContentstackHttpRequest.cs +++ b/Contentstack.Management.Core/Http/ContentstackHttpRequest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using System.Threading.Tasks; using System.Net.Http.Headers; @@ -8,6 +8,7 @@ using System.IO; using Newtonsoft.Json; using Contentstack.Management.Core.Exceptions; +using System.Text.Json; namespace Contentstack.Management.Core.Http { @@ -17,7 +18,8 @@ internal class ContentstackHttpRequest: IHttpRequest private bool _disposed = false; private readonly HttpClient _httpClient; private readonly HttpRequestMessage _request; - private readonly JsonSerializer _serializer; + private readonly Newtonsoft.Json.JsonSerializer _serializer; + private readonly JsonSerializerOptions _stjOptions; #endregion #region Public @@ -54,10 +56,11 @@ public HttpRequestMessage Request #endregion #region Constructor - internal ContentstackHttpRequest(HttpClient httpClient, JsonSerializer serializer) + internal ContentstackHttpRequest(HttpClient httpClient, Newtonsoft.Json.JsonSerializer serializer, JsonSerializerOptions stjOptions = null) { _httpClient = httpClient; _serializer = serializer; + _stjOptions = stjOptions ?? new JsonSerializerOptions(); _request = new HttpRequestMessage(); } #endregion @@ -126,14 +129,14 @@ public async Task GetResponseAsync() if (responseMessage.StatusCode >= HttpStatusCode.Ambiguous && responseMessage.StatusCode < HttpStatusCode.BadRequest) - return new ContentstackResponse(responseMessage, _serializer); + return new ContentstackResponse(responseMessage, _serializer, _stjOptions); if (!responseMessage.IsSuccessStatusCode) { throw ContentstackErrorException.CreateException(responseMessage); } - return new ContentstackResponse(responseMessage, _serializer); + return new ContentstackResponse(responseMessage, _serializer, _stjOptions); } catch (HttpRequestException httpException) { diff --git a/Contentstack.Management.Core/IResponse.cs b/Contentstack.Management.Core/IResponse.cs index 41f1e6d..22b23e1 100644 --- a/Contentstack.Management.Core/IResponse.cs +++ b/Contentstack.Management.Core/IResponse.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Net; using Newtonsoft.Json.Linq; +using System.Text.Json.Nodes; namespace Contentstack.Management.Core { @@ -22,5 +23,18 @@ public interface IResponse JObject OpenJObjectResponse(); TResponse OpenTResponse(); + + /// + /// Opens the response as a System.Text.Json JsonObject. + /// + /// JsonObject representation of the response. + JsonObject OpenJsonObjectResponse(); + + /// + /// Deserializes the response to the specified type using System.Text.Json. + /// + /// The type to deserialize to. + /// Deserialized object of the specified type. + TResponse OpenTResponseStj(); } } diff --git a/Contentstack.Management.Core/Models/BulkOperationModels.cs b/Contentstack.Management.Core/Models/BulkOperationModels.cs index 5b14e62..00f7d6f 100644 --- a/Contentstack.Management.Core/Models/BulkOperationModels.cs +++ b/Contentstack.Management.Core/Models/BulkOperationModels.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models { @@ -255,36 +256,42 @@ public class BulkWorkflowStage /// Gets or sets the workflow stage UID. /// [JsonProperty(propertyName: "uid")] + [JsonPropertyName("uid")] public string Uid { get; set; } /// /// Gets or sets the comment. /// [JsonProperty(propertyName: "comment")] + [JsonPropertyName("comment")] public string Comment { get; set; } /// /// Gets or sets the due date. /// [JsonProperty(propertyName: "due_date")] + [JsonPropertyName("due_date")] public string DueDate { get; set; } /// /// Gets or sets whether to notify. /// [JsonProperty(propertyName: "notify")] + [JsonPropertyName("notify")] public bool Notify { get; set; } /// /// Gets or sets the list of assigned users. /// [JsonProperty(propertyName: "assigned_to")] + [JsonPropertyName("assigned_to")] public List AssignedTo { get; set; } /// /// Gets or sets the list of assigned roles. /// [JsonProperty(propertyName: "assigned_by_roles")] + [JsonPropertyName("assigned_by_roles")] public List AssignedByRoles { get; set; } /// @@ -315,18 +322,21 @@ public class BulkWorkflowUser /// Gets or sets the user UID. /// [JsonProperty(propertyName: "uid")] + [JsonPropertyName("uid")] public string Uid { get; set; } /// /// Gets or sets the user name. /// [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } /// /// Gets or sets the user email. /// [JsonProperty(propertyName: "email")] + [JsonPropertyName("email")] public string Email { get; set; } } @@ -339,12 +349,14 @@ public class BulkWorkflowRole /// Gets or sets the role UID. /// [JsonProperty(propertyName: "uid")] + [JsonPropertyName("uid")] public string Uid { get; set; } /// /// Gets or sets the role name. /// [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } } diff --git a/Contentstack.Management.Core/Models/EntryWorkflowStage.cs b/Contentstack.Management.Core/Models/EntryWorkflowStage.cs index 6dd199b..567a03a 100644 --- a/Contentstack.Management.Core/Models/EntryWorkflowStage.cs +++ b/Contentstack.Management.Core/Models/EntryWorkflowStage.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models { [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] @@ -23,10 +24,13 @@ public class EntryWorkflowStage public class AssignToUser { [JsonProperty(propertyName: "uid")] + [JsonPropertyName("uid")] public string Uid { get; set; } [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } [JsonProperty(propertyName: "email")] + [JsonPropertyName("email")] public string Email { get; set; } } diff --git a/Contentstack.Management.Core/Models/EnvironmentModel.cs b/Contentstack.Management.Core/Models/EnvironmentModel.cs index 7ee8d11..bd34fed 100644 --- a/Contentstack.Management.Core/Models/EnvironmentModel.cs +++ b/Contentstack.Management.Core/Models/EnvironmentModel.cs @@ -1,17 +1,22 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models { [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] public class EnvironmentModel { [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } [JsonProperty(propertyName: "servers")] + [JsonPropertyName("servers")] public List Servers { get; set; } [JsonProperty(propertyName: "urls")] + [JsonPropertyName("urls")] public List Urls { get; set; } [JsonProperty(propertyName: "deploy_content")] + [JsonPropertyName("deploy_content")] public bool DeployContent { get; set; } = true; } @@ -19,14 +24,17 @@ public class EnvironmentModel public class Server { [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } } public class LocalesUrl { [JsonProperty(propertyName: "url")] + [JsonPropertyName("url")] public string Url { get; set; } [JsonProperty(propertyName: "locale")] + [JsonPropertyName("locale")] public string Locale { get; set; } } } diff --git a/Contentstack.Management.Core/Models/LabelModel.cs b/Contentstack.Management.Core/Models/LabelModel.cs index c513136..a6e1efe 100644 --- a/Contentstack.Management.Core/Models/LabelModel.cs +++ b/Contentstack.Management.Core/Models/LabelModel.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models { @@ -7,10 +8,13 @@ namespace Contentstack.Management.Core.Models public class LabelModel { [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } [JsonProperty(propertyName: "parent")] + [JsonPropertyName("parent")] public List Parent { get; set; } [JsonProperty(propertyName: "content_types")] + [JsonPropertyName("content_types")] public List ContentTypes { get; set; } } } diff --git a/Contentstack.Management.Core/Models/LocaleModel.cs b/Contentstack.Management.Core/Models/LocaleModel.cs index 1f90c0a..a185680 100644 --- a/Contentstack.Management.Core/Models/LocaleModel.cs +++ b/Contentstack.Management.Core/Models/LocaleModel.cs @@ -1,5 +1,6 @@ -using System; +using System; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models { @@ -7,10 +8,13 @@ namespace Contentstack.Management.Core.Models public class LocaleModel { [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } [JsonProperty(propertyName: "code")] + [JsonPropertyName("code")] public string Code { get; set; } [JsonProperty(propertyName: "fallback_locale")] + [JsonPropertyName("fallback_locale")] public string FallbackLocale { get; set; } } } diff --git a/Contentstack.Management.Core/Models/OAuthAppAuthorizationResponse.cs b/Contentstack.Management.Core/Models/OAuthAppAuthorizationResponse.cs index 6f875a7..c5d9484 100644 --- a/Contentstack.Management.Core/Models/OAuthAppAuthorizationResponse.cs +++ b/Contentstack.Management.Core/Models/OAuthAppAuthorizationResponse.cs @@ -1,5 +1,6 @@ using System; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models { @@ -10,6 +11,7 @@ public class OAuthAppAuthorizationResponse { [JsonProperty("data")] + [JsonPropertyName("data")] public OAuthAppAuthorizationData[] Data { get; set; } } @@ -20,10 +22,12 @@ public class OAuthAppAuthorizationData { [JsonProperty("authorization_uid")] + [JsonPropertyName("authorization_uid")] public string AuthorizationUid { get; set; } [JsonProperty("user")] + [JsonPropertyName("user")] public OAuthUser User { get; set; } } @@ -32,6 +36,7 @@ public class OAuthUser { [JsonProperty("uid")] + [JsonPropertyName("uid")] public string Uid { get; set; } } } diff --git a/Contentstack.Management.Core/Models/OAuthResponse.cs b/Contentstack.Management.Core/Models/OAuthResponse.cs index 714bc0d..2556445 100644 --- a/Contentstack.Management.Core/Models/OAuthResponse.cs +++ b/Contentstack.Management.Core/Models/OAuthResponse.cs @@ -1,5 +1,6 @@ using System; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models { @@ -10,22 +11,27 @@ public class OAuthResponse { [JsonProperty("access_token")] + [JsonPropertyName("access_token")] public string AccessToken { get; set; } [JsonProperty("refresh_token")] + [JsonPropertyName("refresh_token")] public string RefreshToken { get; set; } [JsonProperty("expires_in")] + [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } [JsonProperty("organization_uid")] + [JsonPropertyName("organization_uid")] public string OrganizationUid { get; set; } [JsonProperty("user_uid")] + [JsonPropertyName("user_uid")] public string UserUid { get; set; } } } diff --git a/Contentstack.Management.Core/Models/Organization.cs b/Contentstack.Management.Core/Models/Organization.cs index db5a540..8e5a485 100644 --- a/Contentstack.Management.Core/Models/Organization.cs +++ b/Contentstack.Management.Core/Models/Organization.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Contentstack.Management.Core.Queryable; @@ -138,7 +138,7 @@ public ContentstackResponse AddUser(List orgInvite, Dictionary AddUserAsync(List orgInvite, D _client.ThrowIfNotLoggedIn(); this.ThrowIfOrganizationUidNull(); - var userInviteService = new UserInvitationService(_client.serializer, this.Uid, "POST"); + var userInviteService = new UserInvitationService(_client.serializer, this.Uid, "POST", stjOptions: _client.SerializerOptions, serializationMode: _client.SerializationMode); if (orgInvite != null) { @@ -217,7 +217,7 @@ public ContentstackResponse RemoveUser(List emails) { throw new ArgumentNullException("emails", CSConstants.EmailsRequired); } - var userInviteService = new UserInvitationService(_client.serializer, this.Uid, "DELETE"); + var userInviteService = new UserInvitationService(_client.serializer, this.Uid, "DELETE", stjOptions: _client.SerializerOptions, serializationMode: _client.SerializationMode); userInviteService.RemoveUsers(emails); return _client.InvokeSync(userInviteService); } @@ -242,7 +242,7 @@ public Task RemoveUserAsync(List emails) { throw new ArgumentNullException("emails", CSConstants.EmailsRequired); } - var userInviteService = new UserInvitationService(_client.serializer, this.Uid, "DELETE"); + var userInviteService = new UserInvitationService(_client.serializer, this.Uid, "DELETE", stjOptions: _client.SerializerOptions, serializationMode: _client.SerializationMode); userInviteService.RemoveUsers(emails); return _client.InvokeAsync(userInviteService); } @@ -265,7 +265,7 @@ public ContentstackResponse ResendInvitation(string shareUid) _client.ThrowIfNotLoggedIn(); this.ThrowIfOrganizationUidNull(); - var userInviteService = new ResendInvitationService(_client.serializer, this.Uid, shareUid); + var userInviteService = new ResendInvitationService(_client.serializer, this.Uid, shareUid, _client.SerializerOptions, _client.SerializationMode); return _client.InvokeSync(userInviteService); } @@ -287,7 +287,7 @@ public Task ResendInvitationAsync(string shareUid) _client.ThrowIfNotLoggedIn(); this.ThrowIfOrganizationUidNull(); - var userInviteService = new ResendInvitationService(_client.serializer, this.Uid, shareUid); + var userInviteService = new ResendInvitationService(_client.serializer, this.Uid, shareUid, _client.SerializerOptions, _client.SerializationMode); return _client.InvokeAsync(userInviteService); } @@ -308,7 +308,7 @@ public ContentstackResponse GetInvitations(ParameterCollection parameter = null) _client.ThrowIfNotLoggedIn(); this.ThrowIfOrganizationUidNull(); - var userInviteService = new UserInvitationService(_client.serializer, this.Uid, "GET", parameter); + var userInviteService = new UserInvitationService(_client.serializer, this.Uid, "GET", parameter, _client.SerializerOptions, _client.SerializationMode); return _client.InvokeSync(userInviteService); } @@ -330,7 +330,7 @@ public Task GetInvitationsAsync(ParameterCollection parame _client.ThrowIfNotLoggedIn(); this.ThrowIfOrganizationUidNull(); - var userInviteService = new UserInvitationService(_client.serializer, this.Uid, "GET", parameter); + var userInviteService = new UserInvitationService(_client.serializer, this.Uid, "GET", parameter, _client.SerializerOptions, _client.SerializationMode); return _client.InvokeAsync(userInviteService); } @@ -352,7 +352,7 @@ public ContentstackResponse TransferOwnership(string email) _client.ThrowIfNotLoggedIn(); this.ThrowIfOrganizationUidNull(); - var service = new TransferOwnershipService(_client.serializer, this.Uid, email); + var service = new TransferOwnershipService(_client.serializer, this.Uid, email, _client.SerializerOptions, _client.SerializationMode); return _client.InvokeSync(service); } @@ -374,7 +374,7 @@ public Task TransferOwnershipAsync(string email) _client.ThrowIfNotLoggedIn(); this.ThrowIfOrganizationUidNull(); - var service = new TransferOwnershipService(_client.serializer, this.Uid, email); + var service = new TransferOwnershipService(_client.serializer, this.Uid, email, _client.SerializerOptions, _client.SerializationMode); return _client.InvokeAsync(service); } diff --git a/Contentstack.Management.Core/Models/PublishRuleModel.cs b/Contentstack.Management.Core/Models/PublishRuleModel.cs index 11b6719..ba01071 100644 --- a/Contentstack.Management.Core/Models/PublishRuleModel.cs +++ b/Contentstack.Management.Core/Models/PublishRuleModel.cs @@ -1,27 +1,37 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models { [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] public class PublishRuleModel { [JsonProperty(propertyName: "workflow")] + [JsonPropertyName("workflow")] public string WorkflowUid { get; set; } [JsonProperty(propertyName: "actions")] + [JsonPropertyName("actions")] public List Actions { get; set; } [JsonProperty(propertyName: "branches")] + [JsonPropertyName("branches")] public List Branches { get; set; } [JsonProperty(propertyName: "content_types")] + [JsonPropertyName("content_types")] public List ContentTypes { get; set; } [JsonProperty(propertyName: "locales")] + [JsonPropertyName("locales")] public List Locales { get; set; } [JsonProperty(propertyName: "environment")] + [JsonPropertyName("environment")] public string Environment { get; set; } [JsonProperty(propertyName: "approvers")] + [JsonPropertyName("approvers")] public Approvals Approvers { get; set; } [JsonProperty(propertyName: "workflow_stage")] + [JsonPropertyName("workflow_stage")] public string WorkflowStageUid { get; set; } [JsonProperty(propertyName: "disable_approver_publishing")] + [JsonPropertyName("disable_approver_publishing")] public bool DisableApproval { get; set; } = false; } @@ -29,8 +39,10 @@ public class PublishRuleModel public class Approvals { [JsonProperty(propertyName: "users")] + [JsonPropertyName("users")] public List Users { get; set; } [JsonProperty(propertyName: "roles")] + [JsonPropertyName("roles")] public List Roles { get; set; } } } diff --git a/Contentstack.Management.Core/Models/RoleModel.cs b/Contentstack.Management.Core/Models/RoleModel.cs index 3fb5ba1..a6ca024 100644 --- a/Contentstack.Management.Core/Models/RoleModel.cs +++ b/Contentstack.Management.Core/Models/RoleModel.cs @@ -1,107 +1,132 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models { [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] public class RoleModel { [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } [JsonProperty(propertyName: "description")] + [JsonPropertyName("description")] public string Description { get; set; } [JsonProperty(propertyName: "rules")] + [JsonPropertyName("rules")] public List Rules { get; set; } [JsonProperty(propertyName: "deploy_content")] + [JsonPropertyName("deploy_content")] public bool DeployContent { get; set; } = true; } public class Rule { [JsonProperty(propertyName: "acl")] + [JsonPropertyName("acl")] public Dictionary ACL { get; } [JsonProperty(propertyName: "restrict")] + [JsonPropertyName("restrict")] public bool Restrict { get; } } public class ContentTypeRules: Rule { [JsonProperty(propertyName: "module")] + [JsonPropertyName("module")] public string Module { get; } = "content_type"; [JsonProperty(propertyName: "content_types")] + [JsonPropertyName("content_types")] public List ContentTypes { get; set; } } public class BranchRules : Rule { [JsonProperty(propertyName: "module")] + [JsonPropertyName("module")] public string Module { get; } = "branch"; [JsonProperty(propertyName: "branches")] + [JsonPropertyName("branches")] public List Branches { get; set; } } public class BranchAliasRules : Rule { [JsonProperty(propertyName: "module")] + [JsonPropertyName("module")] public string Module { get; } = "branch_alias"; [JsonProperty(propertyName: "branch_aliases")] + [JsonPropertyName("branch_aliases")] public List BranchAliases { get; set; } } public class AssetRules : Rule { [JsonProperty(propertyName: "module")] + [JsonPropertyName("module")] public string Module { get; } = "asset"; [JsonProperty(propertyName: "assets")] + [JsonPropertyName("assets")] public List Assets { get; set; } } public class FolderRules : Rule { [JsonProperty(propertyName: "module")] + [JsonPropertyName("module")] public string Module { get; } = "folder"; [JsonProperty(propertyName: "folders")] + [JsonPropertyName("folders")] public List Folders { get; set; } } public class EnvironmentRules : Rule { [JsonProperty(propertyName: "module")] + [JsonPropertyName("module")] public string Module { get; } = "environment"; [JsonProperty(propertyName: "environments")] + [JsonPropertyName("environments")] public List Environments { get; set; } } public class TaxonomyContentType { [JsonProperty(propertyName: "uid")] + [JsonPropertyName("uid")] public string Uid { get; set; } [JsonProperty(propertyName: "acl")] + [JsonPropertyName("acl")] public Dictionary ACL { get; } } public class TaxonomyRules : Rule { [JsonProperty(propertyName: "module")] + [JsonPropertyName("module")] public string Module { get; } = "taxonomy"; [JsonProperty(propertyName: "taxonomies")] + [JsonPropertyName("taxonomies")] public List Taxonomies { get; set; } [JsonProperty(propertyName: "terms")] + [JsonPropertyName("terms")] public List Terms { get; set; } [JsonProperty(propertyName: "content_types")] + [JsonPropertyName("content_types")] public List ContentTypes { get; set; } } } diff --git a/Contentstack.Management.Core/Models/Stack.cs b/Contentstack.Management.Core/Models/Stack.cs index 2c6420b..0ae21d3 100644 --- a/Contentstack.Management.Core/Models/Stack.cs +++ b/Contentstack.Management.Core/Models/Stack.cs @@ -340,7 +340,7 @@ public ContentstackResponse Settings() ThrowIfNotLoggedIn(); ThrowIfAPIKeyEmpty(); - var service = new StackSettingsService(client.serializer, this); + var service = new StackSettingsService(client.serializer, this, stjOptions: client.SerializerOptions, serializationMode: client.SerializationMode); return client.InvokeSync(service); } @@ -361,7 +361,7 @@ public Task SettingsAsync() ThrowIfNotLoggedIn(); ThrowIfAPIKeyEmpty(); - var service = new StackSettingsService(client.serializer, this); + var service = new StackSettingsService(client.serializer, this, stjOptions: client.SerializerOptions, serializationMode: client.SerializationMode); return client.InvokeAsync(service); } @@ -387,7 +387,7 @@ public ContentstackResponse ResetSettings() StackVariables = new Dictionary(), DiscreteVariables = new Dictionary(), Rte = new Dictionary() - }); + }, client.SerializerOptions, client.SerializationMode); return client.InvokeSync(service); } @@ -413,7 +413,7 @@ public Task ResetSettingsAsync() StackVariables = new Dictionary(), DiscreteVariables = new Dictionary(), Rte = new Dictionary() - }); + }, client.SerializerOptions, client.SerializationMode); return client.InvokeAsync(service); } @@ -438,7 +438,7 @@ public ContentstackResponse AddSettings(StackSettings settings) throw new ArgumentNullException("settings", CSConstants.StackSettingsRequired); } - var service = new StackSettingsService(client.serializer, this, "POST", settings); + var service = new StackSettingsService(client.serializer, this, "POST", settings, client.SerializerOptions, client.SerializationMode); return client.InvokeSync(service); } @@ -462,7 +462,7 @@ public Task AddSettingsAsync(StackSettings settings) { throw new ArgumentNullException("settings", CSConstants.StackSettingsRequired); } - var service = new StackSettingsService(client.serializer, this, "POST", settings); + var service = new StackSettingsService(client.serializer, this, "POST", settings, client.SerializerOptions, client.SerializationMode); return client.InvokeAsync(service); } diff --git a/Contentstack.Management.Core/Models/StackSettings.cs b/Contentstack.Management.Core/Models/StackSettings.cs index a8c8f68..f63e8ec 100644 --- a/Contentstack.Management.Core/Models/StackSettings.cs +++ b/Contentstack.Management.Core/Models/StackSettings.cs @@ -1,16 +1,20 @@ -using System; +using System; using System.Collections.Generic; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models { public class StackSettings { [JsonProperty("stack_variables")] + [JsonPropertyName("stack_variables")] public Dictionary StackVariables { get; set; } [JsonProperty("discrete_variables")] + [JsonPropertyName("discrete_variables")] public Dictionary DiscreteVariables { get; set; } [JsonProperty("rte")] + [JsonPropertyName("rte")] public Dictionary Rte { get; set; } } } diff --git a/Contentstack.Management.Core/Models/Token/DeliveryTokenModel.cs b/Contentstack.Management.Core/Models/Token/DeliveryTokenModel.cs index 4bc564c..2e847e2 100644 --- a/Contentstack.Management.Core/Models/Token/DeliveryTokenModel.cs +++ b/Contentstack.Management.Core/Models/Token/DeliveryTokenModel.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models.Token { @@ -8,14 +9,19 @@ namespace Contentstack.Management.Core.Models.Token public class ManagementTokenModel { [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } [JsonProperty(propertyName: "description")] + [JsonPropertyName("description")] public string Description { get; set; } [JsonProperty(propertyName: "Scope")] + [JsonPropertyName("Scope")] public List Scope { get; set; } [JsonProperty(propertyName: "expires_on")] + [JsonPropertyName("expires_on")] public string ExpiresOn { get; set; } [JsonProperty(propertyName: "is_email_notification_enabled")] + [JsonPropertyName("is_email_notification_enabled")] public bool IsEmailNotificationEnabled { get; set; } = false; } @@ -23,10 +29,13 @@ public class ManagementTokenModel public class DeliveryTokenModel { [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } [JsonProperty(propertyName: "description")] + [JsonPropertyName("description")] public string Description { get; set; } [JsonProperty(propertyName: "scope")] + [JsonPropertyName("scope")] public List Scope { get; set; } } @@ -34,6 +43,7 @@ public class DeliveryTokenModel public class DeliveryTokenScope: TokenScope { [JsonProperty(propertyName: "environments")] + [JsonPropertyName("environments")] public List Environments { get; set; } } @@ -41,10 +51,13 @@ public class DeliveryTokenScope: TokenScope public class TokenScope { [JsonProperty(propertyName: "module")] + [JsonPropertyName("module")] public string Module { get; set; } [JsonProperty(propertyName: "acl")] + [JsonPropertyName("acl")] public Dictionary ACL { get; set; } [JsonProperty(propertyName: "branches")] + [JsonPropertyName("branches")] public List Branches { get; set; } } } diff --git a/Contentstack.Management.Core/Models/User.cs b/Contentstack.Management.Core/Models/User.cs index 111ac29..0ec83d5 100644 --- a/Contentstack.Management.Core/Models/User.cs +++ b/Contentstack.Management.Core/Models/User.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Contentstack.Management.Core.Services.User; namespace Contentstack.Management.Core.Models @@ -36,7 +36,7 @@ public ContentstackResponse ForgotPassword(string email) { _client.ThrowIfAlreadyLoggedIn(); - var forgotPassword = new ForgotPasswordService(_client.serializer, email); + var forgotPassword = new ForgotPasswordService(_client.serializer, email, _client.SerializerOptions, _client.SerializationMode); return _client.InvokeSync(forgotPassword); } @@ -57,7 +57,7 @@ public Task ForgotPasswordAsync(string email) { _client.ThrowIfAlreadyLoggedIn(); - var forgotPassword = new ForgotPasswordService(_client.serializer, email); + var forgotPassword = new ForgotPasswordService(_client.serializer, email, _client.SerializerOptions, _client.SerializationMode); return _client.InvokeAsync(forgotPassword); } @@ -80,7 +80,7 @@ public ContentstackResponse ResetPassword(string resetToken, string password, st { _client.ThrowIfAlreadyLoggedIn(); - var resetPassword = new ResetPasswordService(_client.serializer, resetToken, password, confirmPassword); + var resetPassword = new ResetPasswordService(_client.serializer, resetToken, password, confirmPassword, _client.SerializerOptions, _client.SerializationMode); return _client.InvokeSync(resetPassword); } @@ -103,7 +103,7 @@ public Task ResetPasswordAsync(string resetToken, string p { _client.ThrowIfAlreadyLoggedIn(); - var resetPassword = new ResetPasswordService(_client.serializer, resetToken, password, confirmPassword); + var resetPassword = new ResetPasswordService(_client.serializer, resetToken, password, confirmPassword, _client.SerializerOptions, _client.SerializationMode); return _client.InvokeAsync(resetPassword); } diff --git a/Contentstack.Management.Core/Models/WorkflowModel.cs b/Contentstack.Management.Core/Models/WorkflowModel.cs index e7fa086..b196d74 100644 --- a/Contentstack.Management.Core/Models/WorkflowModel.cs +++ b/Contentstack.Management.Core/Models/WorkflowModel.cs @@ -1,22 +1,29 @@ using System.Collections.Generic; using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Contentstack.Management.Core.Models { [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] public class WorkflowModel { [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } [JsonProperty(propertyName: "enabled")] + [JsonPropertyName("enabled")] public bool Enabled { get; set; } = true; [JsonProperty(propertyName: "branches")] + [JsonPropertyName("branches")] public List Branches { get; set; } [JsonProperty(propertyName: "content_types")] + [JsonPropertyName("content_types")] public List ContentTypes { get; set; } [JsonProperty(propertyName: "admin_users")] + [JsonPropertyName("admin_users")] public Dictionary AdminUsers { get; set; } [JsonProperty(propertyName: "workflow_stages")] + [JsonPropertyName("workflow_stages")] public List WorkflowStages { get; set; } } @@ -24,24 +31,34 @@ public class WorkflowModel public class WorkflowStage { [JsonProperty(propertyName: "uid")] + [JsonPropertyName("uid")] public string Uid { get; set; } [JsonProperty(propertyName: "color")] + [JsonPropertyName("color")] public string Color { get; set; } [JsonProperty(propertyName: "SYS_ACL")] + [JsonPropertyName("SYS_ACL")] public Dictionary SystemACL { get; set; } [JsonProperty(propertyName: "next_available_stages")] + [JsonPropertyName("next_available_stages")] public List NextAvailableStages { get; set; } [JsonProperty(propertyName: "allStages")] + [JsonPropertyName("allStages")] public bool AllStages { get; set; } = true; [JsonProperty(propertyName: "allUsers")] + [JsonPropertyName("allUsers")] public bool AllUsers { get; set; } = true; [JsonProperty(propertyName: "specificStages")] + [JsonPropertyName("specificStages")] public bool SpecificStages { get; set; } = false; [JsonProperty(propertyName: "specificUsers")] + [JsonPropertyName("specificUsers")] public bool SpecificUsers { get; set; } = false; [JsonProperty(propertyName: "entry_lock")] + [JsonPropertyName("entry_lock")] public string EntryLock { get; set; } [JsonProperty(propertyName: "name")] + [JsonPropertyName("name")] public string Name { get; set; } } } diff --git a/Contentstack.Management.Core/Services/ContentstackService.cs b/Contentstack.Management.Core/Services/ContentstackService.cs index 7e56fba..fa66203 100644 --- a/Contentstack.Management.Core/Services/ContentstackService.cs +++ b/Contentstack.Management.Core/Services/ContentstackService.cs @@ -8,10 +8,12 @@ using Contentstack.Management.Core.Utils; using Contentstack.Management.Core.Queryable; using System.Net.Http.Headers; +using System.Text.Json; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services { - internal class ContentstackService : IContentstackService + internal class ContentstackService : DualJsonWriter, IContentstackService { #region @@ -23,18 +25,25 @@ internal class ContentstackService : IContentstackService private bool _useQueryString = false; private bool _disposed = false; - private JsonSerializer _serializer { get; set; } + private Newtonsoft.Json.JsonSerializer _serializer { get; set; } + private JsonSerializerSettings _serializerSettings { get; set; } + private JsonSerializerOptions _stjOptions { get; set; } + private SerializationMode _serializationMode { get; set; } #endregion #region Constructor - internal ContentstackService(JsonSerializer serializer, Core.Models.Stack stack = null, ParameterCollection collection = null) + internal ContentstackService(Newtonsoft.Json.JsonSerializer serializer, Core.Models.Stack stack = null, ParameterCollection collection = null, JsonSerializerOptions stjOptions = null, SerializationMode serializationMode = SerializationMode.Newtonsoft) { if (serializer == null) { throw new ArgumentNullException("serializer", CSConstants.JSONSerializerError); } + _stjOptions = stjOptions ?? new JsonSerializerOptions(); + _serializationMode = serializationMode; + _serializerSettings = new JsonSerializerSettings(); + if (stack != null) { if (!string.IsNullOrEmpty(stack.APIKey)) @@ -56,9 +65,25 @@ internal ContentstackService(JsonSerializer serializer, Core.Models.Stack stack this.collection = collection ?? new ParameterCollection(); _serializer = serializer; } + + /// + /// Gets the serialization mode for this service. + /// + protected SerializationMode GetSerializationMode() => _serializationMode; + + /// + /// Gets the Newtonsoft.Json serializer settings. + /// + protected JsonSerializerSettings GetSerializerSettings() => _serializerSettings; + + /// + /// Gets the System.Text.Json serializer options. + /// + protected JsonSerializerOptions GetStjOptions() => _stjOptions; + #endregion - public JsonSerializer Serializer + public Newtonsoft.Json.JsonSerializer Serializer { get { @@ -197,7 +222,7 @@ public virtual IHttpRequest CreateHttpRequest(HttpClient httpClient, Contentstac { Headers["api_version"] = apiVersion; } - var contentstackHttpRequest = new ContentstackHttpRequest(httpClient, _serializer); + var contentstackHttpRequest = new ContentstackHttpRequest(httpClient, _serializer, _stjOptions); contentstackHttpRequest.Method = new HttpMethod(HttpMethod); contentstackHttpRequest.RequestUri = requestUri; diff --git a/Contentstack.Management.Core/Services/DualJsonWriter.cs b/Contentstack.Management.Core/Services/DualJsonWriter.cs new file mode 100644 index 0000000..f08b932 --- /dev/null +++ b/Contentstack.Management.Core/Services/DualJsonWriter.cs @@ -0,0 +1,99 @@ +using System; +using System.IO; +using System.Text; +using System.Globalization; +using Newtonsoft.Json; +using System.Text.Json; +using Contentstack.Management.Core.Enums; + +namespace Contentstack.Management.Core.Services +{ + /// + /// Helper class to store Utf8JsonWriter with its underlying stream. + /// + internal class Utf8JsonWriterInfo + { + public MemoryStream MemoryStream { get; set; } + public Utf8JsonWriter Writer { get; set; } + } + + /// + /// Abstract base class that provides dual JSON writing capabilities + /// for both Newtonsoft.Json and System.Text.Json serialization engines. + /// + public abstract class DualJsonWriter + { + /// + /// Serializes an object using the specified serialization mode and outputs the result as byte content. + /// + /// The object to serialize. + /// The serialization mode to use. + /// Newtonsoft.Json serializer settings. + /// System.Text.Json serializer options. + /// The resulting byte content. + protected void WriteObjectWithBothEngines(object obj, SerializationMode mode, + JsonSerializerSettings newtonsoftSettings, JsonSerializerOptions stjOptions, + out byte[] content) + { + string json = mode switch + { + SerializationMode.SystemTextJson => System.Text.Json.JsonSerializer.Serialize(obj, stjOptions), + _ => JsonConvert.SerializeObject(obj, newtonsoftSettings) + }; + content = Encoding.UTF8.GetBytes(json); + } + + /// + /// Initializes a JSON writer based on the serialization mode for manual JSON construction. + /// Note: For complex scenarios, prefer using WriteObjectWithBothEngines with DTOs instead. + /// + /// The serialization mode to use. + /// The JSON writer object (either JsonTextWriter or stream-based writer info). + /// The string writer (used with Newtonsoft). + protected void WriteManualObjectStart(SerializationMode mode, out object writer, out StringWriter stringWriter) + { + stringWriter = new StringWriter(CultureInfo.InvariantCulture); + + if (mode == SerializationMode.SystemTextJson) + { + // For STJ, we'll store the MemoryStream and Utf8JsonWriter together + var memoryStream = new MemoryStream(); + var utf8Writer = new Utf8JsonWriter(memoryStream); + writer = new Utf8JsonWriterInfo { MemoryStream = memoryStream, Writer = utf8Writer }; + } + else + { + // For Newtonsoft, use JsonTextWriter + writer = new JsonTextWriter(stringWriter); + } + } + + /// + /// Completes the manual JSON writing and returns the byte content. + /// + /// The serialization mode used. + /// The JSON writer object. + /// The string writer (used with Newtonsoft). + /// The resulting byte content. + protected void CompleteManualWrite(SerializationMode mode, object writer, StringWriter stringWriter, out byte[] content) + { + if (mode == SerializationMode.SystemTextJson && writer is Utf8JsonWriterInfo writerInfo) + { + writerInfo.Writer.Flush(); + content = writerInfo.MemoryStream.ToArray(); + writerInfo.Writer.Dispose(); + writerInfo.MemoryStream.Dispose(); + } + else + { + string json = stringWriter.ToString(); + content = Encoding.UTF8.GetBytes(json); + stringWriter.Dispose(); + if (writer is JsonTextWriter jsonWriter) + { + jsonWriter.Close(); + } + } + } + } +} \ No newline at end of file diff --git a/Contentstack.Management.Core/Services/Organization/GetOrganizations.cs b/Contentstack.Management.Core/Services/Organization/GetOrganizations.cs index 70f24e3..f2063bb 100644 --- a/Contentstack.Management.Core/Services/Organization/GetOrganizations.cs +++ b/Contentstack.Management.Core/Services/Organization/GetOrganizations.cs @@ -1,13 +1,15 @@ - -using Contentstack.Management.Core.Queryable; + +using Contentstack.Management.Core.Queryable; using Newtonsoft.Json; +using System.Text.Json; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services.Organization { internal class GetOrganizations: ContentstackService { #region Internal - internal GetOrganizations(JsonSerializer serializer, ParameterCollection collection, string uid = null) : base(serializer, collection: collection) + internal GetOrganizations(Newtonsoft.Json.JsonSerializer serializer, ParameterCollection collection, string uid = null, JsonSerializerOptions stjOptions = null, SerializationMode serializationMode = SerializationMode.Newtonsoft) : base(serializer, collection: collection, stjOptions: stjOptions, serializationMode: serializationMode) { this.ResourcePath = "organizations"; diff --git a/Contentstack.Management.Core/Services/Organization/ResendInvitationService.cs b/Contentstack.Management.Core/Services/Organization/ResendInvitationService.cs index cd19ac0..01f925b 100644 --- a/Contentstack.Management.Core/Services/Organization/ResendInvitationService.cs +++ b/Contentstack.Management.Core/Services/Organization/ResendInvitationService.cs @@ -1,6 +1,8 @@ -using System; +using System; using Newtonsoft.Json; using Contentstack.Management.Core.Utils; +using System.Text.Json; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services.Organization { @@ -8,7 +10,7 @@ internal class ResendInvitationService : ContentstackService { #region Internal - internal ResendInvitationService(JsonSerializer serializer, string uid, string shareUid) : base(serializer) + internal ResendInvitationService(Newtonsoft.Json.JsonSerializer serializer, string uid, string shareUid, JsonSerializerOptions stjOptions = null, SerializationMode serializationMode = SerializationMode.Newtonsoft) : base(serializer, stjOptions: stjOptions, serializationMode: serializationMode) { if (string.IsNullOrEmpty(uid)) { diff --git a/Contentstack.Management.Core/Services/Organization/TransferOwnershipService.cs b/Contentstack.Management.Core/Services/Organization/TransferOwnershipService.cs index be933ed..8409f96 100644 --- a/Contentstack.Management.Core/Services/Organization/TransferOwnershipService.cs +++ b/Contentstack.Management.Core/Services/Organization/TransferOwnershipService.cs @@ -1,8 +1,10 @@ -using System; +using System; using System.Globalization; using System.IO; using Newtonsoft.Json; using Contentstack.Management.Core.Utils; +using System.Text.Json; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services.Organization { @@ -11,7 +13,7 @@ internal class TransferOwnershipService : ContentstackService private readonly string _email; #region Internal - internal TransferOwnershipService(JsonSerializer serializer, string uid, string email) : base(serializer) + internal TransferOwnershipService(Newtonsoft.Json.JsonSerializer serializer, string uid, string email, JsonSerializerOptions stjOptions = null, SerializationMode serializationMode = SerializationMode.Newtonsoft) : base(serializer, stjOptions: stjOptions, serializationMode: serializationMode) { if (string.IsNullOrEmpty(uid)) { @@ -31,16 +33,10 @@ internal TransferOwnershipService(JsonSerializer serializer, string uid, string public override void ContentBody() { - using (StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture)) - { - JsonWriter writer = new JsonTextWriter(stringWriter); - writer.WriteStartObject(); - writer.WritePropertyName("transfer_to"); - writer.WriteValue(_email); - writer.WriteEndObject(); - string snippet = stringWriter.ToString(); - this.ByteContent = System.Text.Encoding.UTF8.GetBytes(snippet); - } + var transferRequest = new { transfer_to = _email }; + var mode = GetSerializationMode(); + WriteObjectWithBothEngines(transferRequest, mode, GetSerializerSettings(), GetStjOptions(), out byte[] content); + this.ByteContent = content; } } } \ No newline at end of file diff --git a/Contentstack.Management.Core/Services/Organization/UserInvitationService.cs b/Contentstack.Management.Core/Services/Organization/UserInvitationService.cs index 7a5dcb5..7e17322 100644 --- a/Contentstack.Management.Core/Services/Organization/UserInvitationService.cs +++ b/Contentstack.Management.Core/Services/Organization/UserInvitationService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -6,6 +6,8 @@ using Contentstack.Management.Core.Queryable; using Newtonsoft.Json; using Contentstack.Management.Core.Utils; +using System.Text.Json; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services.Organization { @@ -16,7 +18,7 @@ internal class UserInvitationService : ContentstackService private List _removeUsers; #region Internal - internal UserInvitationService(JsonSerializer serializer, string uid, string httpMethod = "GET", ParameterCollection collection = null) : base(serializer, collection: collection) + internal UserInvitationService(Newtonsoft.Json.JsonSerializer serializer, string uid, string httpMethod = "GET", ParameterCollection collection = null, JsonSerializerOptions stjOptions = null, SerializationMode serializationMode = SerializationMode.Newtonsoft) : base(serializer, collection: collection, stjOptions: stjOptions, serializationMode: serializationMode) { if (string.IsNullOrEmpty(uid)) { @@ -53,26 +55,69 @@ internal void RemoveUsers(List emails) public override void ContentBody() { - using (StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture)) + object requestObject = null; + + switch (this.HttpMethod) { - switch (this.HttpMethod) - { - case "POST": - addUserJsonWriter(stringWriter); - break; - case "DELETE": - removeUserJsonWriter(stringWriter); - break; - default: - break; - } - string snippet = stringWriter.ToString(); - this.ByteContent = System.Text.Encoding.UTF8.GetBytes(snippet); + case "POST": + requestObject = BuildAddUserRequestObject(); + break; + case "DELETE": + requestObject = BuildRemoveUserRequestObject(); + break; + default: + break; + } + + if (requestObject != null) + { + var mode = GetSerializationMode(); + WriteObjectWithBothEngines(requestObject, mode, GetSerializerSettings(), GetStjOptions(), out byte[] content); + this.ByteContent = content; } } #endregion #region Private + + private object BuildRemoveUserRequestObject() + { + if (_removeUsers != null && _removeUsers.Count > 0) + { + return new { emails = _removeUsers }; + } + return null; + } + + private object BuildAddUserRequestObject() + { + var share = new Dictionary(); + + if (_organizationInvite != null && _organizationInvite.Count > 0) + { + var users = new Dictionary>(); + foreach (UserInvitation invitation in _organizationInvite) + { + users[invitation.Email] = invitation.Roles; + } + share["users"] = users; + } + + if (_stackInvite != null && _stackInvite.Keys.Count > 0) + { + var stacks = new Dictionary(); + foreach (string key in _stackInvite.Keys) + { + foreach (UserInvitation invitation in _stackInvite[key]) + { + stacks[invitation.Email] = new Dictionary> { [key] = invitation.Roles }; + } + } + share["stacks"] = stacks; + } + + return new { share }; + } private void removeUserJsonWriter(StringWriter stringWriter) { if (this._removeUsers != null && this._removeUsers.Count > 0) diff --git a/Contentstack.Management.Core/Services/Serialization/ISerializationEngine.cs b/Contentstack.Management.Core/Services/Serialization/ISerializationEngine.cs new file mode 100644 index 0000000..dd6f401 --- /dev/null +++ b/Contentstack.Management.Core/Services/Serialization/ISerializationEngine.cs @@ -0,0 +1,34 @@ +using System; + +namespace Contentstack.Management.Core.Services.Serialization +{ + /// + /// Abstraction for JSON serialization engines to support both Newtonsoft.Json and System.Text.Json. + /// + public interface ISerializationEngine + { + /// + /// Serializes an object to JSON string. + /// + /// The type of the object to serialize. + /// The object to serialize. + /// JSON string representation of the object. + string Serialize(T obj); + + /// + /// Deserializes JSON string to an object of type T. + /// + /// The type to deserialize to. + /// The JSON string to deserialize. + /// Deserialized object of type T. + T Deserialize(string json); + + /// + /// Deserializes JSON string to an object of the specified type. + /// + /// The JSON string to deserialize. + /// The type to deserialize to. + /// Deserialized object of the specified type. + object Deserialize(string json, Type type); + } +} \ No newline at end of file diff --git a/Contentstack.Management.Core/Services/Serialization/NewtonsoftSerializationEngine.cs b/Contentstack.Management.Core/Services/Serialization/NewtonsoftSerializationEngine.cs new file mode 100644 index 0000000..0878ddc --- /dev/null +++ b/Contentstack.Management.Core/Services/Serialization/NewtonsoftSerializationEngine.cs @@ -0,0 +1,57 @@ +using System; +using Newtonsoft.Json; + +namespace Contentstack.Management.Core.Services.Serialization +{ + /// + /// Newtonsoft.Json implementation of the serialization engine. + /// + public class NewtonsoftSerializationEngine : ISerializationEngine + { + private readonly JsonSerializer _serializer; + private readonly JsonSerializerSettings _settings; + + /// + /// Initializes a new instance of the NewtonsoftSerializationEngine class. + /// + /// The JsonSerializerSettings to use for serialization. + public NewtonsoftSerializationEngine(JsonSerializerSettings settings) + { + _settings = settings ?? new JsonSerializerSettings(); + _serializer = JsonSerializer.Create(_settings); + } + + /// + /// Serializes an object to JSON string using Newtonsoft.Json. + /// + /// The type of the object to serialize. + /// The object to serialize. + /// JSON string representation of the object. + public string Serialize(T obj) + { + return JsonConvert.SerializeObject(obj, _settings); + } + + /// + /// Deserializes JSON string to an object of type T using Newtonsoft.Json. + /// + /// The type to deserialize to. + /// The JSON string to deserialize. + /// Deserialized object of type T. + public T Deserialize(string json) + { + return JsonConvert.DeserializeObject(json, _settings); + } + + /// + /// Deserializes JSON string to an object of the specified type using Newtonsoft.Json. + /// + /// The JSON string to deserialize. + /// The type to deserialize to. + /// Deserialized object of the specified type. + public object Deserialize(string json, Type type) + { + return JsonConvert.DeserializeObject(json, type, _settings); + } + } +} \ No newline at end of file diff --git a/Contentstack.Management.Core/Services/Serialization/SerializationEngineFactory.cs b/Contentstack.Management.Core/Services/Serialization/SerializationEngineFactory.cs new file mode 100644 index 0000000..00e3f00 --- /dev/null +++ b/Contentstack.Management.Core/Services/Serialization/SerializationEngineFactory.cs @@ -0,0 +1,32 @@ +using System.Text.Json; +using Contentstack.Management.Core.Enums; +using Newtonsoft.Json; + +namespace Contentstack.Management.Core.Services.Serialization +{ + /// + /// Factory for creating serialization engines based on the specified mode. + /// + public static class SerializationEngineFactory + { + /// + /// Creates a serialization engine based on the specified mode and settings. + /// + /// The serialization mode to use. + /// Newtonsoft.Json settings (optional). + /// System.Text.Json options (optional). + /// An instance of ISerializationEngine. + public static ISerializationEngine Create( + SerializationMode mode, + JsonSerializerSettings newtonsoftSettings = null, + JsonSerializerOptions stjOptions = null) + { + return mode switch + { + SerializationMode.SystemTextJson => new SystemTextJsonSerializationEngine(stjOptions ?? new JsonSerializerOptions()), + SerializationMode.Auto => new NewtonsoftSerializationEngine(newtonsoftSettings ?? new JsonSerializerSettings()), // Default to Newtonsoft for now + _ => new NewtonsoftSerializationEngine(newtonsoftSettings ?? new JsonSerializerSettings()) + }; + } + } +} \ No newline at end of file diff --git a/Contentstack.Management.Core/Services/Serialization/SystemTextJsonSerializationEngine.cs b/Contentstack.Management.Core/Services/Serialization/SystemTextJsonSerializationEngine.cs new file mode 100644 index 0000000..cec9a6a --- /dev/null +++ b/Contentstack.Management.Core/Services/Serialization/SystemTextJsonSerializationEngine.cs @@ -0,0 +1,55 @@ +using System; +using System.Text.Json; + +namespace Contentstack.Management.Core.Services.Serialization +{ + /// + /// System.Text.Json implementation of the serialization engine. + /// + public class SystemTextJsonSerializationEngine : ISerializationEngine + { + private readonly JsonSerializerOptions _options; + + /// + /// Initializes a new instance of the SystemTextJsonSerializationEngine class. + /// + /// The JsonSerializerOptions to use for serialization. + public SystemTextJsonSerializationEngine(JsonSerializerOptions options) + { + _options = options ?? new JsonSerializerOptions(); + } + + /// + /// Serializes an object to JSON string using System.Text.Json. + /// + /// The type of the object to serialize. + /// The object to serialize. + /// JSON string representation of the object. + public string Serialize(T obj) + { + return JsonSerializer.Serialize(obj, _options); + } + + /// + /// Deserializes JSON string to an object of type T using System.Text.Json. + /// + /// The type to deserialize to. + /// The JSON string to deserialize. + /// Deserialized object of type T. + public T Deserialize(string json) + { + return JsonSerializer.Deserialize(json, _options); + } + + /// + /// Deserializes JSON string to an object of the specified type using System.Text.Json. + /// + /// The JSON string to deserialize. + /// The type to deserialize to. + /// Deserialized object of the specified type. + public object Deserialize(string json, Type type) + { + return JsonSerializer.Deserialize(json, type, _options); + } + } +} \ No newline at end of file diff --git a/Contentstack.Management.Core/Services/Stack/StackCreateUpdateService.cs b/Contentstack.Management.Core/Services/Stack/StackCreateUpdateService.cs index 724c0bd..24ce4ad 100644 --- a/Contentstack.Management.Core/Services/Stack/StackCreateUpdateService.cs +++ b/Contentstack.Management.Core/Services/Stack/StackCreateUpdateService.cs @@ -1,8 +1,11 @@ -using System; +using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using Newtonsoft.Json; using Contentstack.Management.Core.Utils; +using System.Text.Json; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services.Stack { @@ -14,13 +17,15 @@ internal class StackCreateUpdateService : ContentstackService #region Internal internal StackCreateUpdateService( - JsonSerializer serializer, + Newtonsoft.Json.JsonSerializer serializer, Core.Models.Stack stack, string name, string masterLocale = null, string description = null, - string organizationUid = null) - : base(serializer, stack) + string organizationUid = null, + JsonSerializerOptions stjOptions = null, + SerializationMode serializationMode = SerializationMode.Newtonsoft) + : base(serializer, stack, stjOptions: stjOptions, serializationMode: serializationMode) { this.ResourcePath = "/stacks"; @@ -53,37 +58,22 @@ internal StackCreateUpdateService( public override void ContentBody() { - using (StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture)) - { - JsonWriter writer = new JsonTextWriter(stringWriter); - writer.WriteStartObject(); - writer.WritePropertyName("stack"); - writer.WriteStartObject(); - if (!string.IsNullOrEmpty(_name)) - { - writer.WritePropertyName("name"); - writer.WriteValue(_name); - } - if (!string.IsNullOrEmpty(_description)) - { - writer.WritePropertyName("description"); - writer.WriteValue(_description); - } - switch (this.HttpMethod) - { - case "POST": - writer.WritePropertyName("master_locale"); - writer.WriteValue(_masterLocale); - break; - default: - break; - } - writer.WriteEndObject(); - writer.WriteEndObject(); + // Build the stack object dynamically based on what's provided + var stackObj = new Dictionary(); - string snippet = stringWriter.ToString(); - this.ByteContent = System.Text.Encoding.UTF8.GetBytes(snippet); - } + if (!string.IsNullOrEmpty(_name)) + stackObj["name"] = _name; + + if (!string.IsNullOrEmpty(_description)) + stackObj["description"] = _description; + + if (HttpMethod == "POST" && !string.IsNullOrEmpty(_masterLocale)) + stackObj["master_locale"] = _masterLocale; + + var stackRequest = new { stack = stackObj }; + var mode = GetSerializationMode(); + WriteObjectWithBothEngines(stackRequest, mode, GetSerializerSettings(), GetStjOptions(), out byte[] content); + ByteContent = content; } #endregion } diff --git a/Contentstack.Management.Core/Services/Stack/StackSettingsService.cs b/Contentstack.Management.Core/Services/Stack/StackSettingsService.cs index 8855cc7..a1c9eb8 100644 --- a/Contentstack.Management.Core/Services/Stack/StackSettingsService.cs +++ b/Contentstack.Management.Core/Services/Stack/StackSettingsService.cs @@ -1,9 +1,11 @@ -using System; +using System; using System.Globalization; using System.IO; using Contentstack.Management.Core.Models; using Newtonsoft.Json; using Contentstack.Management.Core.Utils; +using System.Text.Json; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services.Stack { @@ -13,7 +15,7 @@ internal class StackSettingsService: ContentstackService private readonly StackSettings _settings; - internal StackSettingsService(JsonSerializer serializer, Core.Models.Stack stack, string method = "GET", StackSettings settings = null) : base(serializer, stack) + internal StackSettingsService(Newtonsoft.Json.JsonSerializer serializer, Core.Models.Stack stack, string method = "GET", StackSettings settings = null, JsonSerializerOptions stjOptions = null, SerializationMode serializationMode = SerializationMode.Newtonsoft) : base(serializer, stack, stjOptions: stjOptions, serializationMode: serializationMode) { if (stack.APIKey == null) { @@ -29,12 +31,13 @@ public override void ContentBody() switch (HttpMethod) { case "POST": - string snippet = $"{{\"stack_settings\":{JsonConvert.SerializeObject(_settings)}}}"; - ByteContent = System.Text.Encoding.UTF8.GetBytes(snippet); + var wrapper = new { stack_settings = _settings }; + var mode = GetSerializationMode(); + WriteObjectWithBothEngines(wrapper, mode, GetSerializerSettings(), GetStjOptions(), out byte[] content); + ByteContent = content; break; default: break; - } } #endregion diff --git a/Contentstack.Management.Core/Services/User/ForgotPasswordService.cs b/Contentstack.Management.Core/Services/User/ForgotPasswordService.cs index e16f9f0..904256c 100644 --- a/Contentstack.Management.Core/Services/User/ForgotPasswordService.cs +++ b/Contentstack.Management.Core/Services/User/ForgotPasswordService.cs @@ -1,8 +1,10 @@ -using System; +using System; using System.Globalization; using System.IO; using Newtonsoft.Json; using Contentstack.Management.Core.Utils; +using System.Text.Json; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services.User { @@ -10,7 +12,7 @@ internal class ForgotPasswordService : ContentstackService { private readonly string _email; - internal ForgotPasswordService(JsonSerializer serializer, string email): base (serializer) + internal ForgotPasswordService(Newtonsoft.Json.JsonSerializer serializer, string email, JsonSerializerOptions stjOptions = null, SerializationMode serializationMode = SerializationMode.Newtonsoft): base (serializer, stjOptions: stjOptions, serializationMode: serializationMode) { if (string.IsNullOrEmpty(email)) { @@ -24,20 +26,17 @@ internal ForgotPasswordService(JsonSerializer serializer, string email): base (s public override void ContentBody() { - using (StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture)) + var forgotPasswordRequest = new { - JsonWriter writer = new JsonTextWriter(stringWriter); - writer.WriteStartObject(); - writer.WritePropertyName("user"); - writer.WriteStartObject(); - writer.WritePropertyName("email"); - writer.WriteValue(_email); - writer.WriteEndObject(); - writer.WriteEndObject(); + user = new + { + email = _email + } + }; - string snippet = stringWriter.ToString(); - this.ByteContent = System.Text.Encoding.UTF8.GetBytes(snippet); - } + var mode = GetSerializationMode(); + WriteObjectWithBothEngines(forgotPasswordRequest, mode, GetSerializerSettings(), GetStjOptions(), out byte[] content); + this.ByteContent = content; } } } diff --git a/Contentstack.Management.Core/Services/User/GetLoggedInUserService.cs b/Contentstack.Management.Core/Services/User/GetLoggedInUserService.cs index 5d76362..3af2836 100644 --- a/Contentstack.Management.Core/Services/User/GetLoggedInUserService.cs +++ b/Contentstack.Management.Core/Services/User/GetLoggedInUserService.cs @@ -1,12 +1,14 @@ -using System; +using System; using Contentstack.Management.Core.Queryable; using Newtonsoft.Json; +using System.Text.Json; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services.User { internal class GetLoggedInUserService: ContentstackService { - public GetLoggedInUserService(JsonSerializer serializer, ParameterCollection collection): base(serializer, collection: collection) + public GetLoggedInUserService(Newtonsoft.Json.JsonSerializer serializer, ParameterCollection collection, JsonSerializerOptions stjOptions = null, SerializationMode serializationMode = SerializationMode.Newtonsoft): base(serializer, collection: collection, stjOptions: stjOptions, serializationMode: serializationMode) { this.ResourcePath = "user"; } diff --git a/Contentstack.Management.Core/Services/User/LoginService.cs b/Contentstack.Management.Core/Services/User/LoginService.cs index 977926e..bd9af24 100644 --- a/Contentstack.Management.Core/Services/User/LoginService.cs +++ b/Contentstack.Management.Core/Services/User/LoginService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Net; using Newtonsoft.Json; @@ -7,6 +7,9 @@ using OtpNet; using Contentstack.Management.Core.Http; using Contentstack.Management.Core.Utils; +using System.Text.Json; +using System.Text.Json.Nodes; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services.User { @@ -18,7 +21,7 @@ internal class LoginService : ContentstackService #endregion #region Constructor - internal LoginService(JsonSerializer serializer, ICredentials credentials, string token = null, string mfaSecret = null): base(serializer) + internal LoginService(Newtonsoft.Json.JsonSerializer serializer, ICredentials credentials, string token = null, string mfaSecret = null, JsonSerializerOptions stjOptions = null, SerializationMode serializationMode = SerializationMode.Newtonsoft): base(serializer, stjOptions: stjOptions, serializationMode: serializationMode) { this.HttpMethod = "POST"; this.ResourcePath = "user-session"; @@ -46,47 +49,93 @@ internal LoginService(JsonSerializer serializer, ICredentials credentials, strin public override void ContentBody() { - using (StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture)) + var credential = _credentials as NetworkCredential; + + // Create login request object - conditionally include tfa_token + object loginRequest; + if (_token != null) { - var credential = _credentials as NetworkCredential; - JsonWriter writer = new JsonTextWriter(stringWriter); - writer.WriteStartObject(); - writer.WritePropertyName("user"); - writer.WriteStartObject(); - writer.WritePropertyName("email"); - writer.WriteValue(credential.UserName); - writer.WritePropertyName("password"); - writer.WriteValue(credential.Password); - if (_token != null) + loginRequest = new { - writer.WritePropertyName("tfa_token"); - writer.WriteValue(_token); - } - writer.WriteEndObject(); - writer.WriteEndObject(); - - string snippet = stringWriter.ToString(); - this.ByteContent = System.Text.Encoding.UTF8.GetBytes(snippet); + user = new + { + email = credential.UserName, + password = credential.Password, + tfa_token = _token + } + }; + } + else + { + loginRequest = new + { + user = new + { + email = credential.UserName, + password = credential.Password + } + }; } + + // Use dual serialization based on mode + var mode = GetSerializationMode(); + WriteObjectWithBothEngines(loginRequest, mode, GetSerializerSettings(), GetStjOptions(), out byte[] content); + this.ByteContent = content; } public override void OnResponse(IResponse httpResponse, ContentstackClientOptions config) { if (httpResponse.IsSuccessStatusCode) { - JObject jObject = httpResponse.OpenJObjectResponse(); - var user = jObject.GetValue("user"); - if (user != null && user.GetType() == typeof(JObject)) + // Try STJ first if available, fallback to Newtonsoft + try { - JObject userObj = (JObject)user; - var authtoken = userObj.GetValue("authtoken"); - if (authtoken != null) + if (GetSerializationMode() == SerializationMode.SystemTextJson) { - config.Authtoken = (string)authtoken; + var jsonObject = httpResponse.OpenJsonObjectResponse(); + var user = jsonObject["user"]; + if (user != null) + { + var userObj = user.AsObject(); + var authtoken = userObj["authtoken"]; + if (authtoken != null) + { + config.Authtoken = authtoken.ToString(); + } + } + } + else + { + // Use existing Newtonsoft implementation + JObject jObject = httpResponse.OpenJObjectResponse(); + var user = jObject.GetValue("user"); + if (user != null && user.GetType() == typeof(JObject)) + { + JObject userObj = (JObject)user; + var authtoken = userObj.GetValue("authtoken"); + if (authtoken != null) + { + config.Authtoken = (string)authtoken; + } + } + } + } + catch + { + // Always fallback to Newtonsoft for safety + JObject jObject = httpResponse.OpenJObjectResponse(); + var user = jObject.GetValue("user"); + if (user != null && user.GetType() == typeof(JObject)) + { + JObject userObj = (JObject)user; + var authtoken = userObj.GetValue("authtoken"); + if (authtoken != null) + { + config.Authtoken = (string)authtoken; + } } } } - } } } diff --git a/Contentstack.Management.Core/Services/User/LogoutService.cs b/Contentstack.Management.Core/Services/User/LogoutService.cs index b5e003a..a0eea47 100644 --- a/Contentstack.Management.Core/Services/User/LogoutService.cs +++ b/Contentstack.Management.Core/Services/User/LogoutService.cs @@ -1,6 +1,8 @@ -using System; +using System; using Newtonsoft.Json; using Contentstack.Management.Core.Utils; +using System.Text.Json; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services.User { @@ -9,7 +11,7 @@ internal class LogoutService : ContentstackService private readonly string _authtoken; #region Constructor - public LogoutService(JsonSerializer serializer, string authtoken): base(serializer) + public LogoutService(Newtonsoft.Json.JsonSerializer serializer, string authtoken, JsonSerializerOptions stjOptions = null, SerializationMode serializationMode = SerializationMode.Newtonsoft): base(serializer, stjOptions: stjOptions, serializationMode: serializationMode) { this.HttpMethod = "DELETE"; this.ResourcePath = "user-session"; diff --git a/Contentstack.Management.Core/Services/User/ResetPasswordService.cs b/Contentstack.Management.Core/Services/User/ResetPasswordService.cs index 799732a..c9ae556 100644 --- a/Contentstack.Management.Core/Services/User/ResetPasswordService.cs +++ b/Contentstack.Management.Core/Services/User/ResetPasswordService.cs @@ -1,8 +1,10 @@ -using System; +using System; using System.Globalization; using System.IO; using Newtonsoft.Json; using Contentstack.Management.Core.Utils; +using System.Text.Json; +using Contentstack.Management.Core.Enums; namespace Contentstack.Management.Core.Services.User { @@ -12,7 +14,7 @@ internal class ResetPasswordService: ContentstackService private readonly string _password; private readonly string _confirmPassword; - internal ResetPasswordService(JsonSerializer serializer, string resetPasswordToken, string password, string confirmPassword) : base(serializer) + internal ResetPasswordService(Newtonsoft.Json.JsonSerializer serializer, string resetPasswordToken, string password, string confirmPassword, JsonSerializerOptions stjOptions = null, SerializationMode serializationMode = SerializationMode.Newtonsoft) : base(serializer, stjOptions: stjOptions, serializationMode: serializationMode) { if (string.IsNullOrEmpty(resetPasswordToken)) { @@ -36,24 +38,19 @@ internal ResetPasswordService(JsonSerializer serializer, string resetPasswordTok public override void ContentBody() { - using (StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture)) + var resetPasswordRequest = new { - JsonWriter writer = new JsonTextWriter(stringWriter); - writer.WriteStartObject(); - writer.WritePropertyName("user"); - writer.WriteStartObject(); - writer.WritePropertyName("reset_password_token"); - writer.WriteValue(_resetPasswordToken); - writer.WritePropertyName("password"); - writer.WriteValue(_password); - writer.WritePropertyName("password_confirmation"); - writer.WriteValue(_confirmPassword); - writer.WriteEndObject(); - writer.WriteEndObject(); + user = new + { + reset_password_token = _resetPasswordToken, + password = _password, + password_confirmation = _confirmPassword + } + }; - string snippet = stringWriter.ToString(); - this.ByteContent = System.Text.Encoding.UTF8.GetBytes(snippet); - } + var mode = GetSerializationMode(); + WriteObjectWithBothEngines(resetPasswordRequest, mode, GetSerializerSettings(), GetStjOptions(), out byte[] content); + this.ByteContent = content; } } } diff --git a/Contentstack.Management.Core/Utils/JsonConversionExtensions.cs b/Contentstack.Management.Core/Utils/JsonConversionExtensions.cs new file mode 100644 index 0000000..e1bc343 --- /dev/null +++ b/Contentstack.Management.Core/Utils/JsonConversionExtensions.cs @@ -0,0 +1,68 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Newtonsoft.Json.Linq; +using Contentstack.Management.Core.Abstractions; + +namespace Contentstack.Management.Core.Utils +{ + /// + /// Extension methods for converting between Newtonsoft.Json and System.Text.Json types. + /// + public static class JsonConversionExtensions + { + /// + /// Converts a Newtonsoft.Json JObject to System.Text.Json JsonObject. + /// + /// The JObject to convert. + /// A JsonObject representation of the input. + public static JsonObject ToJsonObject(this JObject jObject) + { + if (jObject == null) + return null; + + return JsonNode.Parse(jObject.ToString())?.AsObject(); + } + + /// + /// Converts a System.Text.Json JsonObject to Newtonsoft.Json JObject. + /// + /// The JsonObject to convert. + /// A JObject representation of the input. + public static JObject ToJObject(this JsonObject jsonObject) + { + if (jsonObject == null) + return null; + + return JObject.Parse(jsonObject.ToJsonString()); + } + + /// + /// Gets the JsonObject representation of a response using System.Text.Json. + /// + /// The response to convert. + /// A JsonObject representation of the response. + public static JsonObject AsJsonObject(this IResponse response) + { + if (response == null) + return null; + + return response.OpenJObjectResponse().ToJsonObject(); + } + + /// + /// Gets the JObject representation of a response using Newtonsoft.Json. + /// This method is provided for consistency when the response already supports JsonObject. + /// + /// The response to convert. + /// A JObject representation of the response. + public static JObject AsJObject(this IResponse response) + { + if (response == null) + return null; + + // Check if response supports JsonObject natively (for future implementation) + // For now, use the existing JObject method + return response.OpenJObjectResponse(); + } + } +} \ No newline at end of file diff --git a/Contentstack.Management.Core/contentstack.management.core.csproj b/Contentstack.Management.Core/contentstack.management.core.csproj index f422b69..d2eee49 100644 --- a/Contentstack.Management.Core/contentstack.management.core.csproj +++ b/Contentstack.Management.Core/contentstack.management.core.csproj @@ -55,6 +55,7 @@ + @@ -68,11 +69,13 @@ + + diff --git a/Directory.Build.props b/Directory.Build.props index 355f623..cee9232 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,5 @@ - 0.10.0 + 0.11.0-beta.1