Skip to content

Commit e3b8de3

Browse files
authored
Merge pull request #70 from contentstack/development
DX | 29-06-2026 | Release
2 parents 266e4f3 + 7b38932 commit e3b8de3

9 files changed

Lines changed: 717 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
### Version: 1.4.0
2+
#### Date: June-23-2026
3+
- Added `EmbeddedObject` as a concrete implementation of `IEmbeddedObject`, covering both `IEmbeddedEntry` and `IEmbeddedAsset`.
4+
- Added `EmbeddedObjectConverter` to resolve `IEmbeddedObject` during JSON deserialization without requiring changes in consumer code.
5+
- Custom fields on embedded entries and assets are preserved via `[JsonExtensionData]`.
6+
17
### Version: 1.3.0
28

39
#### Date: May-11-2026

Contentstack.Utils.Tests/EmbeddedObjectConverterTest.cs

Lines changed: 400 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"uid": "sample_asset_uid",
3+
"_content_type_uid": "sys_assets",
4+
"title": "Dummy Image Asset",
5+
"filename": "dummy-image.jpg",
6+
"url": "https://example.com/assets/dummy-image.jpg",
7+
"content_type": "image/jpeg",
8+
"file_size": "10000",
9+
"locale": "en-us",
10+
"is_dir": false,
11+
"tags": ["test", "dummy"],
12+
"dimension": {
13+
"height": 100,
14+
"width": 100
15+
},
16+
"publish_details": {
17+
"environment": "test",
18+
"locale": "en-us",
19+
"time": "2021-01-01T00:00:00.000Z",
20+
"user": "sample_user_uid"
21+
}
22+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"uid": "sample_author_uid",
3+
"_content_type_uid": "author",
4+
"title": "Dummy User",
5+
"locale": "en-us",
6+
"_version": 1,
7+
"created_at": "2021-01-01T00:00:00.000Z",
8+
"updated_at": "2021-01-01T00:00:00.000Z",
9+
"bio": "This is a dummy bio used for testing purposes.",
10+
"email": "dummy.user@example.com",
11+
"avatar_url": "https://example.com/dummy-avatar.jpg",
12+
"social": {
13+
"twitter": "@dummyuser",
14+
"linkedin": "linkedin.com/in/dummyuser"
15+
},
16+
"tags": ["test", "dummy", "sample"],
17+
"publish_details": {
18+
"environment": "test",
19+
"locale": "en-us",
20+
"time": "2021-01-01T00:00:00.000Z",
21+
"user": "sample_user_uid"
22+
}
23+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
[
2+
{
3+
"uid": "sample_author_uid",
4+
"_content_type_uid": "author",
5+
"title": "Dummy User",
6+
"locale": "en-us",
7+
"_version": 1,
8+
"bio": "This is a dummy bio used for testing purposes.",
9+
"email": "dummy.user@example.com",
10+
"avatar_url": "https://example.com/dummy-avatar.jpg",
11+
"social": {
12+
"twitter": "@dummyuser",
13+
"linkedin": "linkedin.com/in/dummyuser"
14+
},
15+
"tags": ["test", "dummy", "sample"],
16+
"publish_details": {
17+
"environment": "test",
18+
"locale": "en-us",
19+
"time": "2021-01-01T00:00:00.000Z",
20+
"user": "sample_user_uid"
21+
}
22+
},
23+
{
24+
"uid": "sample_asset_uid",
25+
"_content_type_uid": "sys_assets",
26+
"title": "Dummy Image Asset",
27+
"filename": "dummy-image.jpg",
28+
"url": "https://example.com/assets/dummy-image.jpg",
29+
"content_type": "image/jpeg",
30+
"file_size": "10000",
31+
"locale": "en-us",
32+
"is_dir": false,
33+
"tags": ["test", "dummy"],
34+
"dimension": {
35+
"height": 100,
36+
"width": 100
37+
},
38+
"publish_details": {
39+
"environment": "test",
40+
"locale": "en-us",
41+
"time": "2021-01-01T00:00:00.000Z",
42+
"user": "sample_user_uid"
43+
}
44+
},
45+
{
46+
"uid": "sample_asset_uid_2",
47+
"_content_type_uid": "sys_assets",
48+
"title": "Dummy PNG Asset",
49+
"filename": "dummy-image.png",
50+
"url": "https://example.com/assets/dummy-image.png",
51+
"content_type": "image/png",
52+
"file_size": "5000",
53+
"locale": "en-us",
54+
"is_dir": false,
55+
"tags": ["test", "dummy"],
56+
"dimension": {
57+
"height": 100,
58+
"width": 100
59+
},
60+
"publish_details": {
61+
"environment": "test",
62+
"locale": "en-us",
63+
"time": "2021-01-01T00:00:00.000Z",
64+
"user": "sample_user_uid_2"
65+
}
66+
}
67+
]
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
{
2+
"entry": {
3+
"uid": "sample_rte_entry_uid",
4+
"_content_type_uid": "rte_json_comp",
5+
"title": "Test RTE Entry",
6+
"locale": "en-us",
7+
"_version": 1,
8+
"created_at": "2021-01-01T00:00:00.000Z",
9+
"updated_at": "2021-01-01T00:00:00.000Z",
10+
"author_name": "Test Author",
11+
"category": "Test Category",
12+
"rte_json": {
13+
"type": "doc",
14+
"children": [
15+
{
16+
"type": "p",
17+
"children": [
18+
{ "text": "Featured author: " },
19+
{
20+
"type": "reference",
21+
"attrs": {
22+
"type": "entry",
23+
"entry-uid": "sample_author_uid",
24+
"content-type-uid": "author",
25+
"locale": "en-us",
26+
"display-type": "inline"
27+
},
28+
"children": [{ "text": "" }]
29+
}
30+
]
31+
},
32+
{
33+
"type": "p",
34+
"children": [{ "text": "Sample image below:" }]
35+
},
36+
{
37+
"type": "reference",
38+
"attrs": {
39+
"type": "asset",
40+
"asset-uid": "sample_asset_uid",
41+
"content-type-uid": "sys_assets",
42+
"display-type": "display"
43+
},
44+
"children": [{ "text": "" }]
45+
},
46+
{
47+
"type": "reference",
48+
"attrs": {
49+
"type": "asset",
50+
"asset-uid": "sample_asset_uid_2",
51+
"content-type-uid": "sys_assets",
52+
"display-type": "display"
53+
},
54+
"children": [{ "text": "" }]
55+
}
56+
]
57+
},
58+
"_embedded_items": {
59+
"rte_json": [
60+
{
61+
"uid": "sample_author_uid",
62+
"_content_type_uid": "author",
63+
"title": "Dummy User",
64+
"locale": "en-us",
65+
"_version": 1,
66+
"bio": "This is a dummy bio used for testing purposes.",
67+
"email": "dummy.user@example.com",
68+
"avatar_url": "https://example.com/dummy-avatar.jpg",
69+
"social": {
70+
"twitter": "@dummyuser",
71+
"linkedin": "linkedin.com/in/dummyuser"
72+
},
73+
"tags": ["test", "dummy", "sample"],
74+
"publish_details": {
75+
"environment": "test",
76+
"locale": "en-us",
77+
"time": "2021-01-01T00:00:00.000Z",
78+
"user": "sample_user_uid"
79+
}
80+
},
81+
{
82+
"uid": "sample_asset_uid",
83+
"_content_type_uid": "sys_assets",
84+
"title": "Dummy Image Asset",
85+
"filename": "dummy-image.jpg",
86+
"url": "https://example.com/assets/dummy-image.jpg",
87+
"content_type": "image/jpeg",
88+
"file_size": "10000",
89+
"locale": "en-us",
90+
"is_dir": false,
91+
"tags": ["test", "dummy"],
92+
"dimension": {
93+
"height": 100,
94+
"width": 100
95+
},
96+
"publish_details": {
97+
"environment": "test",
98+
"locale": "en-us",
99+
"time": "2021-01-01T00:00:00.000Z",
100+
"user": "sample_user_uid"
101+
}
102+
},
103+
{
104+
"uid": "sample_asset_uid_2",
105+
"_content_type_uid": "sys_assets",
106+
"title": "Dummy PNG Asset",
107+
"filename": "dummy-image.png",
108+
"url": "https://example.com/assets/dummy-image.png",
109+
"content_type": "image/png",
110+
"file_size": "5000",
111+
"locale": "en-us",
112+
"is_dir": false,
113+
"tags": ["test", "dummy"],
114+
"dimension": {
115+
"height": 100,
116+
"width": 100
117+
},
118+
"publish_details": {
119+
"environment": "test",
120+
"locale": "en-us",
121+
"time": "2021-01-01T00:00:00.000Z",
122+
"user": "sample_user_uid_2"
123+
}
124+
}
125+
]
126+
},
127+
"publish_details": {
128+
"environment": "test",
129+
"locale": "en-us",
130+
"time": "2021-01-01T00:00:00.000Z",
131+
"user": "sample_user_uid"
132+
}
133+
}
134+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using Contentstack.Utils.Interfaces;
3+
using Contentstack.Utils.Models;
4+
using Newtonsoft.Json;
5+
using Newtonsoft.Json.Linq;
6+
7+
namespace Contentstack.Utils.Converters
8+
{
9+
// Resolves IEmbeddedObject to EmbeddedObject during deserialization.
10+
// CanConvert uses exact type match so customer-defined subclasses are not intercepted.
11+
public class EmbeddedObjectConverter : JsonConverter
12+
{
13+
public override bool CanConvert(Type objectType)
14+
=> objectType == typeof(IEmbeddedObject);
15+
16+
public override bool CanWrite => false;
17+
18+
public override object ReadJson(JsonReader reader, Type objectType,
19+
object existingValue, JsonSerializer serializer)
20+
{
21+
if (reader.TokenType == JsonToken.Null)
22+
return null;
23+
24+
var jo = JObject.Load(reader);
25+
var result = new EmbeddedObject();
26+
serializer.Populate(jo.CreateReader(), result);
27+
return result;
28+
}
29+
30+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
31+
=> throw new NotSupportedException(
32+
"EmbeddedObjectConverter is read-only. Serialize EmbeddedObject directly.");
33+
}
34+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Collections.Generic;
2+
using Contentstack.Utils.Interfaces;
3+
using Newtonsoft.Json;
4+
using Newtonsoft.Json.Linq;
5+
namespace Contentstack.Utils.Models
6+
{
7+
// Concrete class used by EmbeddedObjectConverter when deserializing _embedded_items.
8+
// Implements both IEmbeddedEntry and IEmbeddedAsset to cover entries and assets.
9+
public class EmbeddedObject : IEmbeddedEntry, IEmbeddedAsset
10+
{
11+
[JsonProperty("uid")]
12+
public string Uid { get; set; } = string.Empty;
13+
14+
[JsonProperty("_content_type_uid")]
15+
public string ContentTypeUid { get; set; } = string.Empty;
16+
17+
[JsonProperty("title")]
18+
public string Title { get; set; } = string.Empty;
19+
20+
[JsonProperty("filename")]
21+
public string FileName { get; set; } = string.Empty;
22+
23+
[JsonProperty("url")]
24+
public string Url { get; set; } = string.Empty;
25+
26+
// Any field not explicitly declared above (custom fields, locale data, etc.)
27+
[JsonExtensionData]
28+
public IDictionary<string, JToken> Fields { get; set; } = new Dictionary<string, JToken>();
29+
}
30+
}

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>1.3.0</Version>
3+
<Version>1.4.0</Version>
44
</PropertyGroup>
55
</Project>

0 commit comments

Comments
 (0)