Skip to content

Commit c6c0f5f

Browse files
committed
Added ability to manually specify properties and exclude them from code generation
1 parent c8e3a82 commit c6c0f5f

File tree

4 files changed

+132
-2
lines changed

4 files changed

+132
-2
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
<LangVersion>9.0</LangVersion>
44
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
55
<Nullable>enable</Nullable>
6-
<Version>0.4.0</Version>
6+
<Version>0.5.0</Version>
77
</PropertyGroup>
88
</Project>

JsonByExampleGenerator.Generator/JsonGenerator.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System.Globalization;
1616
using Scriban;
1717
using JsonByExampleGenerator.Generator.Utils;
18+
using System.Text.Json.Serialization;
1819

1920
namespace JsonByExampleGenerator.Generator
2021
{
@@ -87,6 +88,11 @@ public void Execute(GeneratorExecutionContext context)
8788
template = Template.Parse(EmbeddedResource.GetContent(defaultTemplatePath), defaultTemplatePath);
8889
}
8990

91+
if (context.Compilation != null)
92+
{
93+
FilterAndChangeBasedOnExistingCode(classModels, namespaceName, context.Compilation);
94+
}
95+
9096
// Use Scriban to render the code using the model that was built
9197
string generatedCode = template.Render(new
9298
{
@@ -117,6 +123,40 @@ public void Execute(GeneratorExecutionContext context)
117123
}
118124
}
119125

126+
/// <summary>
127+
/// If json properties are specified manually, filter them here, so they can be omitted when generating code.
128+
/// </summary>
129+
/// <param name="classModels">The list of class models to apply filtering to</param>
130+
/// <param name="namespaceName">The namespace, so we know what existing types to resolve</param>
131+
/// <param name="compilation">The compilation, so we can find existing types</param>
132+
private void FilterAndChangeBasedOnExistingCode(List<ClassModel> classModels, string namespaceName, Compilation compilation)
133+
{
134+
foreach(var classModel in classModels)
135+
{
136+
// Find a class in the current compilation that already exists
137+
var existingClass = compilation.GetTypeByMetadataName($"{namespaceName}.Json.{classModel.ClassName}");
138+
if(existingClass != null)
139+
{
140+
// Find all JsonPropertyName decorations
141+
var jsonProperties = existingClass
142+
.GetMembers()
143+
.OfType<IPropertySymbol>()
144+
.SelectMany(m => m
145+
.GetAttributes()
146+
.Where(a =>
147+
string.Equals(nameof(JsonPropertyNameAttribute), a.AttributeClass?.Name, StringComparison.InvariantCulture)
148+
|| string.Equals("JsonPropertyName", a.AttributeClass?.Name, StringComparison.InvariantCulture))
149+
.Select(a => a.ConstructorArguments.FirstOrDefault().Value?.ToString())
150+
.Where(a => a != null));
151+
if(jsonProperties != null)
152+
{
153+
// Remove properties that are already in the compilation; no need to generate them
154+
classModel.Properties.RemoveAll(p => jsonProperties.Contains(p.PropertyNameOriginal));
155+
}
156+
}
157+
}
158+
}
159+
120160
/// <summary>
121161
/// Find out if Microsoft.Extensions.Configuration.Json is used.
122162
/// </summary>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Xunit;
7+
using Xunit.Abstractions;
8+
9+
namespace JsonByExampleGenerator.Tests
10+
{
11+
public class ChangesToGeneratedCodeTests : TestsBase
12+
{
13+
public ChangesToGeneratedCodeTests(ITestOutputHelper output) : base(output)
14+
{
15+
}
16+
17+
[Fact]
18+
public void ShouldGenerateWithChangedPropertyTypeOrName()
19+
{
20+
string source = @"using System;
21+
using System.Text.Json.Serialization;
22+
23+
namespace TestImplementation.Json
24+
{
25+
public partial class Example
26+
{
27+
// Change the type to int, instead of string
28+
[JsonPropertyName(""prop"")]
29+
public int Prop { get; set; }
30+
31+
// Change the name of the property, but reference json property properly
32+
[JsonPropertyName(""another"")]
33+
public int YetAnother { get; set; }
34+
}
35+
}
36+
37+
namespace Example
38+
{
39+
class Test
40+
{
41+
public static string RunTest()
42+
{
43+
var json = new TestImplementation.Json.Example()
44+
{
45+
Prop = 111,
46+
YetAnother = 33
47+
};
48+
return $""{json.Prop} {json.YetAnother}"";
49+
}
50+
}
51+
}";
52+
var compilation = GetGeneratedOutput(source, new Dictionary<string, string>()
53+
{
54+
{ "example.json", "{ \"prop\" : \"val\", \"another\" : 22 }" }
55+
});
56+
57+
Assert.Equal("111 33", RunTest(compilation));
58+
}
59+
}
60+
}

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,34 @@ If you have specific needs for the generated code, you can easily create a [Scri
109109
- Create a file with the same name as your example json file, but with the extension `.sbntxt` instead of `.json`.
110110
- Ensure that the file is included in the `AdditionalFiles` for your project (the same way that you include your json files).
111111
- Copy the contents of the [default template](JsonByExampleGenerator.Generator/JsonByExampleTemplate.sbntxt), paste them in the file and save.
112-
- Change the template in any way you want, and you should observe the changes when you build your project.
112+
- Change the template in any way you want, and you should observe the changes when you build your project.
113+
114+
## Manually change the type or name of properties
115+
116+
Sometimes you may not like the name of the property or the type that is used. For example, if you want a `DateTime` instead of a `string` or a `int` instead of a `double`. The generator cannot detect this based on examples.
117+
118+
You can fix this by specifying the property exactly how you want it in a partial class. Example:
119+
120+
Given the following `products.json` file:
121+
```json
122+
[
123+
{
124+
"id": 12,
125+
"name": "Example product"
126+
}
127+
]
128+
```
129+
130+
You can specify this partial class:
131+
132+
```csharp
133+
namespace MyNs.Json
134+
{
135+
public partial class Product
136+
{
137+
// Based on the attribute, the generator knows not to generate this property
138+
[JsonPropertyName("id")]
139+
public int Id { get; set; }
140+
}
141+
}
142+
```

0 commit comments

Comments
 (0)