Skip to content

Commit c08fbe0

Browse files
committed
feat(#128): immutable attributes
1 parent c131839 commit c08fbe0

File tree

3 files changed

+90
-21
lines changed

3 files changed

+90
-21
lines changed

src/JsonApiDotNetCore/Models/AttrAttribute.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,22 @@ namespace JsonApiDotNetCore.Models
66
{
77
public class AttrAttribute : Attribute
88
{
9-
public AttrAttribute(string publicName)
9+
public AttrAttribute(string publicName, bool isImmutable = false)
1010
{
1111
PublicAttributeName = publicName;
12+
IsImmutable = isImmutable;
1213
}
1314

14-
public AttrAttribute(string publicName, string internalName)
15+
public AttrAttribute(string publicName, string internalName, bool isImmutable = false)
1516
{
1617
PublicAttributeName = publicName;
1718
InternalAttributeName = internalName;
19+
IsImmutable = isImmutable;
1820
}
1921

2022
public string PublicAttributeName { get; set; }
2123
public string InternalAttributeName { get; set; }
24+
public bool IsImmutable { get; set; }
2225

2326
public object GetValue(object entity)
2427
{

src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ private object SetEntityAttributes(
116116
{
117117
var convertedValue = ConvertAttrValue(newValue, entityProperty.PropertyType);
118118
entityProperty.SetValue(entity, convertedValue);
119-
_jsonApiContext.AttributesToUpdate[attr] = convertedValue;
119+
120+
if(attr.IsImmutable == false)
121+
_jsonApiContext.AttributesToUpdate[attr] = convertedValue;
120122
}
121123
}
122124

test/UnitTests/Serialization/JsonApiDeSerializerTests.cs

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,24 @@ public void Can_Deserialize_Complex_Types()
2626
jsonApiContextMock.SetupAllProperties();
2727
jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph);
2828
jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary<AttrAttribute, object>());
29-
jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions {
30-
JsonContractResolver = new CamelCasePropertyNamesContractResolver()
29+
jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions
30+
{
31+
JsonContractResolver = new CamelCasePropertyNamesContractResolver()
3132
});
3233

3334
var genericProcessorFactoryMock = new Mock<IGenericProcessorFactory>();
3435

3536
var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object);
3637

37-
var content = new Document {
38-
Data = new DocumentData {
38+
var content = new Document
39+
{
40+
Data = new DocumentData
41+
{
3942
Type = "test-resource",
4043
Id = "1",
4144
Attributes = new Dictionary<string, object> {
42-
{
43-
"complex-member", new { compoundName = "testName" }
45+
{
46+
"complex-member", new { compoundName = "testName" }
4447
}
4548
}
4649
}
@@ -66,23 +69,26 @@ public void Can_Deserialize_Complex_List_Types()
6669
jsonApiContextMock.SetupAllProperties();
6770
jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph);
6871
jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary<AttrAttribute, object>());
69-
jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions {
70-
JsonContractResolver = new CamelCasePropertyNamesContractResolver()
72+
jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions
73+
{
74+
JsonContractResolver = new CamelCasePropertyNamesContractResolver()
7175
});
7276

7377
var genericProcessorFactoryMock = new Mock<IGenericProcessorFactory>();
7478

7579
var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object);
7680

77-
var content = new Document {
78-
Data = new DocumentData {
81+
var content = new Document
82+
{
83+
Data = new DocumentData
84+
{
7985
Type = "test-resource",
8086
Id = "1",
8187
Attributes = new Dictionary<string, object> {
82-
{
83-
"complex-members", new [] {
88+
{
89+
"complex-members", new [] {
8490
new { compoundName = "testName" }
85-
}
91+
}
8692
}
8793
}
8894
}
@@ -110,20 +116,23 @@ public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs()
110116
jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph);
111117
jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary<AttrAttribute, object>());
112118

113-
jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions {
114-
JsonContractResolver = new DasherizedResolver() // <---
119+
jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions
120+
{
121+
JsonContractResolver = new DasherizedResolver() // <---
115122
});
116123

117124
var genericProcessorFactoryMock = new Mock<IGenericProcessorFactory>();
118125

119126
var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object);
120127

121-
var content = new Document {
122-
Data = new DocumentData {
128+
var content = new Document
129+
{
130+
Data = new DocumentData
131+
{
123132
Type = "test-resource",
124133
Id = "1",
125134
Attributes = new Dictionary<string, object> {
126-
{
135+
{
127136
"complex-member", new Dictionary<string, string> { { "compound-name", "testName" } }
128137
}
129138
}
@@ -138,10 +147,65 @@ public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs()
138147
Assert.Equal("testName", result.ComplexMember.CompoundName);
139148
}
140149

150+
[Fact]
151+
public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate()
152+
{
153+
// arrange
154+
var contextGraphBuilder = new ContextGraphBuilder();
155+
contextGraphBuilder.AddResource<TestResource>("test-resource");
156+
var contextGraph = contextGraphBuilder.Build();
157+
158+
var attributesToUpdate = new Dictionary<AttrAttribute, object>();
159+
160+
var jsonApiContextMock = new Mock<IJsonApiContext>();
161+
jsonApiContextMock.SetupAllProperties();
162+
jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph);
163+
jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate);
164+
165+
jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions
166+
{
167+
JsonContractResolver = new DasherizedResolver()
168+
});
169+
170+
var genericProcessorFactoryMock = new Mock<IGenericProcessorFactory>();
171+
172+
var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object);
173+
174+
var content = new Document
175+
{
176+
Data = new DocumentData
177+
{
178+
Type = "test-resource",
179+
Id = "1",
180+
Attributes = new Dictionary<string, object> {
181+
{ "complex-member", new Dictionary<string, string> {
182+
{ "compound-name", "testName" } }
183+
},
184+
{ "immutable", "value"}
185+
}
186+
}
187+
};
188+
189+
var contentString = JsonConvert.SerializeObject(content);
190+
191+
// act
192+
var result = deserializer.Deserialize<TestResource>(contentString);
193+
194+
// assert
195+
Assert.NotNull(result.ComplexMember);
196+
Assert.Equal(1, attributesToUpdate.Count);
197+
198+
foreach(var attr in attributesToUpdate)
199+
Assert.False(attr.Key.IsImmutable);
200+
}
201+
141202
private class TestResource : Identifiable
142203
{
143204
[Attr("complex-member")]
144205
public ComplexType ComplexMember { get; set; }
206+
207+
[Attr("immutable", isImmutable: true)]
208+
public string Immutable { get; set; }
145209
}
146210

147211
private class TestResourceWithList : Identifiable

0 commit comments

Comments
 (0)