diff --git a/CHANGELOG.md b/CHANGELOG.md
index a265a5f..037950b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+### Version: 1.1.0
+#### Date: March-24-2026
+- Added `GetVariantAliases` and `GetDataCsvariantsAttribute` for variant alias extraction and `data-csvariants` serialization; Invalid arguments throw `ArgumentException`.
+
+
### Version: 1.0.7
#### Date: January-12-2026
- Improved error messages
diff --git a/Contentstack.Utils.Tests/Contentstack.Utils.Tests.csproj b/Contentstack.Utils.Tests/Contentstack.Utils.Tests.csproj
index 45ab904..ee46f08 100644
--- a/Contentstack.Utils.Tests/Contentstack.Utils.Tests.csproj
+++ b/Contentstack.Utils.Tests/Contentstack.Utils.Tests.csproj
@@ -37,4 +37,7 @@
+
+
+
diff --git a/Contentstack.Utils.Tests/Resources/variantsEntries.json b/Contentstack.Utils.Tests/Resources/variantsEntries.json
new file mode 100644
index 0000000..09cc7a5
--- /dev/null
+++ b/Contentstack.Utils.Tests/Resources/variantsEntries.json
@@ -0,0 +1,71 @@
+{
+ "entries": [
+ {
+ "uid": "entry_uid_1",
+ "_metadata": {},
+ "locale": "en-us",
+ "_version": 1,
+ "title": "Sample Movie",
+ "publish_details": {
+ "time": "2025-12-11T07:56:17.574Z",
+ "user": "test_user",
+ "environment": "test_env",
+ "locale": "en-us",
+ "variants": {
+ "cs_variant_0_0": {
+ "alias": "cs_personalize_0_0",
+ "environment": "test_env",
+ "time": "2025-12-11T07:56:17.574Z",
+ "locale": "en-us",
+ "user": "test_user",
+ "version": 1
+ },
+ "cs_variant_0_3": {
+ "alias": "cs_personalize_0_3",
+ "environment": "test_env",
+ "time": "2025-12-11T07:56:17.582Z",
+ "locale": "en-us",
+ "user": "test_user",
+ "version": 1
+ }
+ }
+ }
+ },
+ {
+ "uid": "entry_uid_2",
+ "_metadata": {},
+ "locale": "en-us",
+ "_version": 2,
+ "title": "Another Movie",
+ "publish_details": {
+ "time": "2025-12-11T07:10:19.964Z",
+ "user": "test_user",
+ "environment": "test_env",
+ "locale": "en-us",
+ "variants": {
+ "cs_variant_0_0": {
+ "alias": "cs_personalize_0_0",
+ "environment": "test_env",
+ "time": "2025-12-11T07:10:19.964Z",
+ "locale": "en-us",
+ "user": "test_user",
+ "version": 2
+ }
+ }
+ }
+ },
+ {
+ "uid": "entry_uid_3",
+ "_metadata": {},
+ "locale": "en-us",
+ "_version": 1,
+ "title": "Movie No Variants",
+ "publish_details": {
+ "time": "2025-11-20T10:00:00.000Z",
+ "user": "test_user",
+ "environment": "test_env",
+ "locale": "en-us"
+ }
+ }
+ ]
+ }
diff --git a/Contentstack.Utils.Tests/Resources/variantsSingleEntry.json b/Contentstack.Utils.Tests/Resources/variantsSingleEntry.json
new file mode 100644
index 0000000..ddb0e22
--- /dev/null
+++ b/Contentstack.Utils.Tests/Resources/variantsSingleEntry.json
@@ -0,0 +1,39 @@
+{
+ "entry": {
+ "uid": "entry_uid_single",
+ "_metadata": {},
+ "locale": "en-us",
+ "_version": 1,
+ "ACL": {},
+ "_in_progress": false,
+ "title": "Sample Movie",
+ "created_at": "2025-11-20T10:00:00.000Z",
+ "updated_at": "2025-12-11T07:56:17.574Z",
+ "created_by": "test_user",
+ "updated_by": "test_user",
+ "publish_details": {
+ "time": "2025-12-11T07:56:17.574Z",
+ "user": "test_user",
+ "environment": "test_env",
+ "locale": "en-us",
+ "variants": {
+ "cs_variant_0_0": {
+ "alias": "cs_personalize_0_0",
+ "environment": "test_env",
+ "time": "2025-12-11T07:56:17.574Z",
+ "locale": "en-us",
+ "user": "test_user",
+ "version": 1
+ },
+ "cs_variant_0_3": {
+ "alias": "cs_personalize_0_3",
+ "environment": "test_env",
+ "time": "2025-12-11T07:56:17.582Z",
+ "locale": "en-us",
+ "user": "test_user",
+ "version": 1
+ }
+ }
+ }
+ }
+ }
diff --git a/Contentstack.Utils.Tests/VariantAliasesTest.cs b/Contentstack.Utils.Tests/VariantAliasesTest.cs
new file mode 100644
index 0000000..09cecf5
--- /dev/null
+++ b/Contentstack.Utils.Tests/VariantAliasesTest.cs
@@ -0,0 +1,257 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Contentstack.Utils.Tests
+{
+ public class VariantAliasesTest
+ {
+ private static JObject ReadJsonRoot(string fileName)
+ {
+ var path = Path.Combine(AppContext.BaseDirectory, "Resources", fileName);
+ return JObject.Parse(File.ReadAllText(path));
+ }
+
+ private static HashSet JsonArrayToStringSet(JArray arr)
+ {
+ var set = new HashSet();
+ foreach (var t in arr)
+ {
+ set.Add(t.ToString());
+ }
+ return set;
+ }
+
+ [Fact]
+ public void GetVariantAliases_SingleEntry_ReturnsAliases()
+ {
+ JObject full = ReadJsonRoot("variantsSingleEntry.json");
+ JObject entry = (JObject)full["entry"];
+ const string contentTypeUid = "movie";
+
+ JObject result = Utils.GetVariantAliases(entry, contentTypeUid);
+
+ Assert.True(result["entry_uid"] != null && !string.IsNullOrEmpty(result["entry_uid"].ToString()));
+ Assert.Equal(contentTypeUid, result["contenttype_uid"].ToString());
+ JArray variants = (JArray)result["variants"];
+ Assert.NotNull(variants);
+ var aliasSet = JsonArrayToStringSet(variants);
+ Assert.Equal(
+ new HashSet { "cs_personalize_0_0", "cs_personalize_0_3" },
+ aliasSet);
+ }
+
+ [Fact]
+ public void GetDataCsvariantsAttribute_SingleEntry_ReturnsJsonArrayString()
+ {
+ JObject full = ReadJsonRoot("variantsSingleEntry.json");
+ JObject entry = (JObject)full["entry"];
+ const string contentTypeUid = "movie";
+
+ JObject result = Utils.GetDataCsvariantsAttribute(entry, contentTypeUid);
+
+ Assert.True(result["data-csvariants"] != null);
+ string dataCsvariantsStr = result["data-csvariants"].ToString();
+ JArray arr = JArray.Parse(dataCsvariantsStr);
+ Assert.Single(arr);
+ JObject first = (JObject)arr[0];
+ Assert.True(first["entry_uid"] != null && !string.IsNullOrEmpty(first["entry_uid"].ToString()));
+ Assert.Equal(contentTypeUid, first["contenttype_uid"].ToString());
+ var aliasSet = JsonArrayToStringSet((JArray)first["variants"]);
+ Assert.Equal(
+ new HashSet { "cs_personalize_0_0", "cs_personalize_0_3" },
+ aliasSet);
+ }
+
+ [Fact]
+ public void GetVariantAliases_MultipleEntries_ReturnsOneResultPerEntryWithUid()
+ {
+ JObject full = ReadJsonRoot("variantsEntries.json");
+ JArray entries = (JArray)full["entries"];
+ const string contentTypeUid = "movie";
+
+ JArray result = Utils.GetVariantAliases(entries, contentTypeUid);
+
+ Assert.NotNull(result);
+ Assert.Equal(3, result.Count);
+
+ JObject first = (JObject)result[0];
+ Assert.True(first["entry_uid"] != null && !string.IsNullOrEmpty(first["entry_uid"].ToString()));
+ Assert.Equal(contentTypeUid, first["contenttype_uid"].ToString());
+ var firstSet = JsonArrayToStringSet((JArray)first["variants"]);
+ Assert.Equal(
+ new HashSet { "cs_personalize_0_0", "cs_personalize_0_3" },
+ firstSet);
+
+ JObject second = (JObject)result[1];
+ Assert.True(second["entry_uid"] != null && !string.IsNullOrEmpty(second["entry_uid"].ToString()));
+ Assert.Single((JArray)second["variants"]);
+ Assert.Equal("cs_personalize_0_0", ((JArray)second["variants"])[0].ToString());
+
+ JObject third = (JObject)result[2];
+ Assert.True(third["entry_uid"] != null && !string.IsNullOrEmpty(third["entry_uid"].ToString()));
+ Assert.Empty((JArray)third["variants"]);
+ }
+
+ [Fact]
+ public void GetDataCsvariantsAttribute_MultipleEntries_ReturnsJsonArrayString()
+ {
+ JObject full = ReadJsonRoot("variantsEntries.json");
+ JArray entries = (JArray)full["entries"];
+ const string contentTypeUid = "movie";
+
+ JObject result = Utils.GetDataCsvariantsAttribute(entries, contentTypeUid);
+
+ Assert.True(result["data-csvariants"] != null);
+ string dataCsvariantsStr = result["data-csvariants"].ToString();
+ JArray arr = JArray.Parse(dataCsvariantsStr);
+ Assert.Equal(3, arr.Count);
+ Assert.True(((JObject)arr[0])["entry_uid"] != null && !string.IsNullOrEmpty(((JObject)arr[0])["entry_uid"].ToString()));
+ Assert.Equal(2, ((JArray)((JObject)arr[0])["variants"]).Count);
+ Assert.True(((JObject)arr[1])["entry_uid"] != null && !string.IsNullOrEmpty(((JObject)arr[1])["entry_uid"].ToString()));
+ Assert.Single((JArray)((JObject)arr[1])["variants"]);
+ Assert.True(((JObject)arr[2])["entry_uid"] != null && !string.IsNullOrEmpty(((JObject)arr[2])["entry_uid"].ToString()));
+ Assert.Empty((JArray)((JObject)arr[2])["variants"]);
+ }
+
+ [Fact]
+ public void GetVariantAliases_ThrowsWhenEntryNull()
+ {
+ Assert.Throws(() => Utils.GetVariantAliases((JObject)null, "landing_page"));
+ }
+
+ [Fact]
+ public void GetVariantAliases_ThrowsWhenContentTypeUidNull()
+ {
+ JObject full = ReadJsonRoot("variantsSingleEntry.json");
+ JObject entry = (JObject)full["entry"];
+ Assert.Throws(() => Utils.GetVariantAliases(entry, null));
+ }
+
+ [Fact]
+ public void GetVariantAliases_ThrowsWhenContentTypeUidEmpty()
+ {
+ JObject full = ReadJsonRoot("variantsSingleEntry.json");
+ JObject entry = (JObject)full["entry"];
+ Assert.Throws(() => Utils.GetVariantAliases(entry, ""));
+ }
+
+ [Fact]
+ public void GetDataCsvariantsAttribute_WhenEntryNull_ReturnsEmptyArrayString()
+ {
+ JObject result = Utils.GetDataCsvariantsAttribute((JObject)null, "landing_page");
+ Assert.True(result["data-csvariants"] != null);
+ Assert.Equal("[]", result["data-csvariants"].ToString());
+ }
+
+ [Fact]
+ public void GetVariantAliases_ThrowsWhenUidMissing()
+ {
+ var entry = new JObject { ["title"] = "no-uid" };
+ Assert.Throws(() => Utils.GetVariantAliases(entry, "movie"));
+ }
+
+ [Fact]
+ public void GetVariantAliases_ThrowsWhenUidNull()
+ {
+ var entry = new JObject { ["uid"] = JValue.CreateNull() };
+ Assert.Throws(() => Utils.GetVariantAliases(entry, "movie"));
+ }
+
+ [Fact]
+ public void GetVariantAliases_Batch_ThrowsWhenContentTypeUidNull()
+ {
+ var entries = new JArray { new JObject { ["uid"] = "a" } };
+ Assert.Throws(() => Utils.GetVariantAliases(entries, null));
+ }
+
+ [Fact]
+ public void GetVariantAliases_Batch_ThrowsWhenContentTypeUidEmpty()
+ {
+ var entries = new JArray { new JObject { ["uid"] = "a" } };
+ Assert.Throws(() => Utils.GetVariantAliases(entries, ""));
+ }
+
+ [Fact]
+ public void GetDataCsvariantsAttribute_WhenEntriesArrayNull_ReturnsEmptyArrayString()
+ {
+ JObject result = Utils.GetDataCsvariantsAttribute((JArray)null, "movie");
+ Assert.Equal("[]", result["data-csvariants"].ToString());
+ }
+
+ [Fact]
+ public void GetDataCsvariantsAttribute_Batch_ThrowsWhenContentTypeUidNull()
+ {
+ var entries = new JArray { new JObject { ["uid"] = "a" } };
+ Assert.Throws(() => Utils.GetDataCsvariantsAttribute(entries, null));
+ }
+
+ [Fact]
+ public void GetDataCsvariantsAttribute_Batch_ThrowsWhenContentTypeUidEmpty()
+ {
+ var entries = new JArray { new JObject { ["uid"] = "a" } };
+ Assert.Throws(() => Utils.GetDataCsvariantsAttribute(entries, ""));
+ }
+
+ [Fact]
+ public void GetVariantAliases_ReturnsEmptyVariantsWhenPublishDetailsMissing()
+ {
+ var entry = new JObject { ["uid"] = "blt_no_pd" };
+ JObject result = Utils.GetVariantAliases(entry, "movie");
+ Assert.Equal("blt_no_pd", result["entry_uid"].ToString());
+ Assert.Equal("movie", result["contenttype_uid"].ToString());
+ Assert.Empty((JArray)result["variants"]);
+ }
+
+ [Fact]
+ public void GetVariantAliases_ReturnsEmptyVariantsWhenVariantsObjectEmpty()
+ {
+ var entry = new JObject
+ {
+ ["uid"] = "blt_empty_v",
+ ["publish_details"] = new JObject
+ {
+ ["variants"] = new JObject()
+ }
+ };
+ JObject result = Utils.GetVariantAliases(entry, "movie");
+ Assert.Empty((JArray)result["variants"]);
+ }
+
+ [Fact]
+ public void GetVariantAliases_ReturnsEmptyVariantsWhenVariantsKeyMissing()
+ {
+ var entry = new JObject
+ {
+ ["uid"] = "blt_no_variants_key",
+ ["publish_details"] = new JObject { ["time"] = "2025-01-01T00:00:00.000Z" }
+ };
+ JObject result = Utils.GetVariantAliases(entry, "movie");
+ Assert.Empty((JArray)result["variants"]);
+ }
+
+ [Fact]
+ public void GetVariantAliases_SkipsVariantWhenAliasMissingOrEmpty()
+ {
+ var entry = new JObject
+ {
+ ["uid"] = "blt_skip",
+ ["publish_details"] = new JObject
+ {
+ ["variants"] = new JObject
+ {
+ ["v1"] = new JObject { ["alias"] = "keep_me" },
+ ["v2"] = new JObject(),
+ ["v3"] = new JObject { ["alias"] = "" }
+ }
+ }
+ };
+ JObject result = Utils.GetVariantAliases(entry, "page");
+ var variants = (JArray)result["variants"];
+ Assert.Single(variants);
+ Assert.Equal("keep_me", variants[0].ToString());
+ }
+ }
+}
diff --git a/Contentstack.Utils/Utils.cs b/Contentstack.Utils/Utils.cs
index 81f4c00..821df2c 100644
--- a/Contentstack.Utils/Utils.cs
+++ b/Contentstack.Utils/Utils.cs
@@ -1,10 +1,12 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using Contentstack.Utils.Models;
using HtmlAgilityPack;
using Contentstack.Utils.Extensions;
using Contentstack.Utils.Interfaces;
using System;
using Contentstack.Utils.Enums;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
namespace Contentstack.Utils
{
@@ -286,5 +288,112 @@ private static object GetParentTagsValue(string dataValue, bool tagsAsObject)
return $"data-cslp-parent-field={dataValue}";
}
}
+
+ public static JObject GetVariantAliases(JObject entry, string contentTypeUid)
+ {
+ if (string.IsNullOrEmpty(contentTypeUid))
+ {
+ throw new ArgumentException("ContentType is required.");
+ }
+ if (entry == null)
+ {
+ throw new ArgumentException("Entry must not be null.");
+ }
+ if (!entry.ContainsKey("uid") || entry["uid"] == null || entry["uid"].Type == JTokenType.Null)
+ {
+ throw new ArgumentException("Entry must contain uid.");
+ }
+
+ string entryUid = entry["uid"]?.ToString() ?? "";
+ JArray variantsArray = ExtractVariantAliasesFromEntry(entry);
+ JObject result = new JObject
+ {
+ ["entry_uid"] = entryUid,
+ ["contenttype_uid"] = contentTypeUid,
+ ["variants"] = variantsArray
+ };
+ return result;
+ }
+
+ public static JArray GetVariantAliases(JArray entries, string contentTypeUid)
+ {
+ if (string.IsNullOrEmpty(contentTypeUid))
+ {
+ throw new ArgumentException("ContentType is required.");
+ }
+ if (entries == null)
+ {
+ return new JArray();
+ }
+ JArray variantResults = new JArray();
+ foreach (JToken token in entries)
+ {
+ JObject entry = token as JObject;
+ if (entry != null && entry.ContainsKey("uid") && entry["uid"] != null && entry["uid"].Type != JTokenType.Null)
+ {
+ variantResults.Add(GetVariantAliases(entry, contentTypeUid));
+ }
+ }
+ return variantResults;
+ }
+
+ public static JObject GetDataCsvariantsAttribute(JObject entry, string contentTypeUid)
+ {
+ if (entry == null)
+ {
+ JObject result = new JObject();
+ result["data-csvariants"] = "[]";
+ return result;
+ }
+ JArray entries = new JArray();
+ entries.Add(entry);
+ return GetDataCsvariantsAttribute(entries, contentTypeUid);
+ }
+
+ public static JObject GetDataCsvariantsAttribute(JArray entries, string contentTypeUid)
+ {
+ JObject result = new JObject();
+ if (entries == null)
+ {
+ result["data-csvariants"] = "[]";
+ return result;
+ }
+ if (string.IsNullOrEmpty(contentTypeUid))
+ {
+ throw new ArgumentException("ContentType is required.");
+ }
+
+ JArray variantResults = GetVariantAliases(entries, contentTypeUid);
+ result["data-csvariants"] = variantResults.ToString(Formatting.None);
+ return result;
+ }
+
+ private static JArray ExtractVariantAliasesFromEntry(JObject entry)
+ {
+ JArray variantArray = new JArray();
+ JObject publishDetails = entry["publish_details"] as JObject;
+ if (publishDetails == null)
+ {
+ return variantArray;
+ }
+ JObject variants = publishDetails["variants"] as JObject;
+ if (variants == null)
+ {
+ return variantArray;
+ }
+
+ foreach (JProperty prop in variants.Properties())
+ {
+ if (prop.Value is JObject valueObj)
+ {
+ string alias = valueObj["alias"]?.ToString();
+ if (!string.IsNullOrEmpty(alias))
+ {
+ variantArray.Add(alias.Trim());
+ }
+ }
+ }
+ return variantArray;
+ }
}
}