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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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<T>()`
- 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -85,5 +86,36 @@ public TResponse OpenTResponse<TResponse>()
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<TResponse>()
{
if (string.IsNullOrEmpty(_responseContent))
return default(TResponse);

try
{
return System.Text.Json.JsonSerializer.Deserialize<TResponse>(_responseContent);
}
catch
{
return default(TResponse);
}
}
}
}
29 changes: 22 additions & 7 deletions Contentstack.Management.Core/ContentstackClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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;
Expand All @@ -53,6 +58,16 @@ public class ContentstackClient : IContentstackClient
/// </summary>
public JsonSerializerSettings SerializerSettings { get; set; } = new JsonSerializerSettings();

/// <summary>
/// Get and Set method for System.Text.Json serialization options.
/// </summary>
public JsonSerializerOptions SerializerOptions { get; set; } = new JsonSerializerOptions();

/// <summary>
/// Get and Set method for controlling which serialization engine to use.
/// </summary>
public SerializationMode SerializationMode { get; set; } = SerializationMode.Newtonsoft;

#endregion

#region Constructor
Expand Down Expand Up @@ -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);
}
Expand All @@ -394,7 +409,7 @@ public ContentstackResponse Login(ICredentials credentials, string token = null,
public Task<ContentstackResponse> 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<LoginService, ContentstackResponse>(Login);
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -452,7 +467,7 @@ public ContentstackResponse Logout(string authtoken = null)
public Task<ContentstackResponse> 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<LogoutService, ContentstackResponse>(logout);
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -696,7 +711,7 @@ public Task<ContentstackResponse> GetUserAsync(ParameterCollection collection =
{
ThrowIfNotLoggedIn();

GetLoggedInUserService getUser = new GetLoggedInUserService(serializer, collection);
GetLoggedInUserService getUser = new GetLoggedInUserService(serializer, collection, SerializerOptions, SerializationMode);

return InvokeAsync<GetLoggedInUserService, ContentstackResponse>(getUser);
}
Expand Down
32 changes: 29 additions & 3 deletions Contentstack.Management.Core/ContentstackResponse.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
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
{
Expand All @@ -20,7 +22,8 @@
Dictionary<string, string> _headers;
HashSet<string> _headerNamesSet;
private readonly HttpResponseMessage _response;
private readonly JsonSerializer _serializer;
private readonly Newtonsoft.Json.JsonSerializer _serializer;
private readonly JsonSerializerOptions _stjOptions;

#region Public
/// <summary>
Expand Down Expand Up @@ -126,10 +129,11 @@
}
#endregion

internal ContentstackResponse(HttpResponseMessage response, JsonSerializer serializer)
internal ContentstackResponse(HttpResponseMessage response, Newtonsoft.Json.JsonSerializer serializer, JsonSerializerOptions stjOptions = null)

Check warning on line 132 in Contentstack.Management.Core/ContentstackResponse.cs

View workflow job for this annotation

GitHub Actions / unit-test

Cannot convert null literal to non-nullable reference type.
{
_response = response;
_serializer = serializer;
_stjOptions = stjOptions ?? new JsonSerializerOptions();

this.StatusCode = response.StatusCode;
this.IsSuccessStatusCode = response.IsSuccessStatusCode;
Expand Down Expand Up @@ -175,6 +179,28 @@
return jObject.ToObject<TResponse>(_serializer);
}

/// <summary>
/// System.Text.Json JsonObject format response.
/// </summary>
/// <returns>The JsonObject.</returns>
public JsonObject OpenJsonObjectResponse()
{
ThrowIfDisposed();
return JsonNode.Parse(OpenResponse())?.AsObject();
}

/// <summary>
/// Type response to serialize the response using System.Text.Json.
/// </summary>
/// <typeparam name="TResponse">The type to serialize the response into.</typeparam>
/// <returns></returns>
public TResponse OpenTResponseStj<TResponse>()
{
ThrowIfDisposed();
string json = OpenResponse();
return System.Text.Json.JsonSerializer.Deserialize<TResponse>(json, _stjOptions);
}


#region Dispose method
public void Dispose()
Expand Down
23 changes: 23 additions & 0 deletions Contentstack.Management.Core/Enums/SerializationMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Contentstack.Management.Core.Enums
{
/// <summary>
/// Defines the serialization mode for JSON operations.
/// </summary>
public enum SerializationMode
{
/// <summary>
/// Use Newtonsoft.Json for serialization (default for backward compatibility).
/// </summary>
Newtonsoft = 0,

/// <summary>
/// Use System.Text.Json for serialization.
/// </summary>
SystemTextJson = 1,

/// <summary>
/// Auto-detect best serialization mode based on context (future implementation).
/// </summary>
Auto = 2
}
}
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -42,6 +43,7 @@ public class ContentstackErrorException: Exception
/// This is error message.
/// </summary>
[JsonProperty("error_message")]
[JsonPropertyName("error_message")]
public string ErrorMessage
{
get
Expand All @@ -59,12 +61,14 @@ public string ErrorMessage
/// This is error code.
/// </summary>
[JsonProperty("error_code")]
[JsonPropertyName("error_code")]
public int ErrorCode { get; set; }

/// <summary>
/// Set of errors in detail.
/// </summary>
[JsonProperty("errors")]
[JsonPropertyName("errors")]
public Dictionary<string, object> Errors { get; set; }

/// <summary>
Expand Down
13 changes: 8 additions & 5 deletions Contentstack.Management.Core/Http/ContentstackHttpRequest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Net.Http.Headers;
Expand All @@ -8,6 +8,7 @@
using System.IO;
using Newtonsoft.Json;
using Contentstack.Management.Core.Exceptions;
using System.Text.Json;

namespace Contentstack.Management.Core.Http
{
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -126,14 +129,14 @@ public async Task<IResponse> 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)
{
Expand Down
16 changes: 15 additions & 1 deletion Contentstack.Management.Core/IResponse.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System;
using System.Net;
using Newtonsoft.Json.Linq;
using System.Text.Json.Nodes;

namespace Contentstack.Management.Core
{
Expand All @@ -22,5 +23,18 @@ public interface IResponse
JObject OpenJObjectResponse();

TResponse OpenTResponse<TResponse>();

/// <summary>
/// Opens the response as a System.Text.Json JsonObject.
/// </summary>
/// <returns>JsonObject representation of the response.</returns>
JsonObject OpenJsonObjectResponse();

/// <summary>
/// Deserializes the response to the specified type using System.Text.Json.
/// </summary>
/// <typeparam name="TResponse">The type to deserialize to.</typeparam>
/// <returns>Deserialized object of the specified type.</returns>
TResponse OpenTResponseStj<TResponse>();
}
}
Loading
Loading