JsonConstGenerator 1.0.0

dotnet add package JsonConstGenerator --version 1.0.0
                    
NuGet\Install-Package JsonConstGenerator -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="JsonConstGenerator" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="JsonConstGenerator" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="JsonConstGenerator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add JsonConstGenerator --version 1.0.0
                    
#r "nuget: JsonConstGenerator, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package JsonConstGenerator@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=JsonConstGenerator&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=JsonConstGenerator&version=1.0.0
                    
Install as a Cake Tool

JsonConstGenerator

JsonConstGenerator is a C# Source Generator that converts JSON files into strongly-typed, compile-time accessible node structures.

It allows developers to define hierarchical identifiers and values in JSON and access them in C# through a generated, strongly typed tree. The generator mirrors the JSON structure and exposes each node through a minimal, readable generated structure.

The generated code is designed to remain easy to inspect, while also providing helper methods for fast node lookup without reflection.


Installation

Install the generator package:

dotnet add package JsonConstGenerator

Design Goals

JsonConstGenerator aims to provide:

  • Strongly typed access to JSON-defined identifiers
  • Compile-time generation with no reflection
  • Minimal and readable generated code
  • Efficient runtime lookup of nodes
  • A clear tree structure that mirrors the original JSON

Example Use Cases

Common use cases include:

  • Permission identifiers
  • Feature flags
  • Event names
  • Configuration keys
  • Logging categories
  • Localization keys
  • API identifiers

Basic Usage

1. Create a JSON file

Example:

{ 
    "Numbers": { 
        "IntValue": 42, 
        "LargeIntValue": 5000000000, 
        "DecimalValue": 3.1415 
    }, 
    "Strings": { 
        "Greeting": "Hello world" 
    }, 
    "Flags": { 
        "IsEnabled": true 
    }, 
    //A comment placed above a property is copied in the node tree
    "EmptyObject": {}, 
    "NullValue": null, 
    "EmptyArray": [], 
    "Permissions": ["Read", "Write", "Delete"] 
}

2. Add the JSON file as an AdditionalFiles item

<ItemGroup>
  <AdditionalFiles Include="myJsonFile.json" />
</ItemGroup>

3. Declare a partial root class


namespace SourceNamespace;

[JsonConstGenerator("myJsonFile.json")]
public static partial class MyConstants
{
}

During compilation the generator creates the node tree.


Generated Structure

The generator produces a minimal tree structure that mirrors the JSON hierarchy. The structure is intentionally written in a clear object-initializer form so it is easy to inspect.

Example generated code:

namespace SourceNamespace
{
    // Generated by myJsonFile.json 
    public static partial class MyConstants 
    { 
        public static readonly Numbers Numbers = new() 
        { 
            IntValue = new("Numbers.IntValue", 42), 
            LargeIntValue = new("Numbers.LargeIntValue", 5000000000L), 
            DecimalValue = new("Numbers.DecimalValue", 3.1415) 
        }; 
        
        public static readonly Strings Strings = new() 
        { 
            Greeting = new("Strings.Greeting", "Hello world") 
        }; 
        
        public static readonly Flags Flags = new() 
        { 
            IsEnabled = new("Flags.IsEnabled", true) 
        }; 
        
        ///<summary>
        ///A comment placed above a property is copied in the node tree
        ///</summary>
        public static readonly ValueLessConstNode EmptyObject = new("EmptyObject"); 
        
        public static readonly ValueLessConstNode NullValue = new("NullValue"); 
        
        public static readonly ValueLessConstNode EmptyArray = new("EmptyArray"); 
        
        public static readonly Permissions Permissions = new() 
        { 
            Read = new("Permissions.Read"), 
            Write = new("Permissions.Write"), 
            Delete = new("Permissions.Delete") 
        }; 
        

        

        public static IEnumerable<IConstNode> GetAllNodes() 
        {
            foreach (var node in _allNodes)
                yield return node;

            foreach (var child in Strings.GetAllNodes())
                yield return child;

            foreach (var child in Flags.GetAllNodes())
                yield return child;

            foreach (var child in Permissions.GetAllNodes())
                yield return child;

            foreach (var child in Numbers.GetAllNodes())
                yield return child;

            foreach (var child in Numbers.GetAllNodes())
                yield return child;

        }
        public static IEnumerable<IConstNode> GetAllEndNodes() 
        {
            foreach (var node in _allEndNodes)
                yield return node;

            foreach (var child in Strings.GetAllEndNodes())
                yield return child;

            foreach (var child in Flags.GetAllEndNodes())
                yield return child;

            foreach (var child in Permissions.GetAllEndNodes())
                yield return child;

            foreach (var child in Numbers.GetAllEndNodes())
                yield return child;

            foreach (var child in Numbers.GetAllEndNodes())
                yield return child;
        }

        ///<summary>
        /// A function that finds a single node
        ///</summary>
        public static IConstNode FindNode(string path) => AllNodes().First(x => x.NodePath == path);

        public static bool TryFindNode(string path, out IConstNode node)
        {
        ...
        }


        private static readonly IConstNode[] _allNodes = 
        { 
            Numbers, 
            Strings, 
            Flags, 
            EmptyObject, 
            NullValue, 
            EmptyArray, 
            Permissions, 
         }; 

        private static readonly IConstNode[] _allEndNodes = 
        { 
            EmptyObject, 
            NullValue, 
            EmptyArray, 
         }; 
    } 
}

The node classes themselves are generated separately and represent the tree structure.


Attribute Usage

To generate constants from JSON, apply the JsonConstGenerator attribute to a partial class.

using JsonConstGenerator;

[JsonConstGenerator("constants.json")]
public static partial class MyConstants
{
}

During compilation, the generator reads the provided JSON file(s) and generates a strongly typed node structure inside the class.


Multiple JSON Files

The attribute supports multiple file paths:

[JsonConstGenerator("base.json", "overrides.json")]
public static partial class MyConstants
{
}

When multiple files are provided:

  • Files are processed in the order they are defined
  • All files are merged into a single structure
  • In case of conflicts, later files overwrite earlier ones

Example

base.json

{
  "Feature": {
    "Enabled": false
  }
}

overrides.json

{
  "Feature": {
    "Enabled": true
  }
}

Result:

MyConstants.Feature.Enabled // true

Wildcard Support

The attribute supports simple wildcard patterns using *:

[JsonConstGenerator("constants/*.json")]
public static partial class MyConstants
{
}

This allows you to include multiple files without listing them individually.

Requirements

All matched files must be included as AdditionalFiles in your project:

<ItemGroup>
  <AdditionalFiles Include="constants\*.json" />
</ItemGroup>

Behavior

  • Wildcards are matched against files provided via AdditionalFiles
  • Matching files are processed in deterministic order

Path Resolution

All file paths are resolved relative to the project root.

Example:

[JsonConstGenerator("constants/settings.json")]

Resolves to:

<ProjectRoot>/constants/settings.json

Paths are not relative to the file where the attribute is used.


Path Separator

Each node has a NodePath representing its position in the hierarchy.

By default, the separator is ".":

MyConstants.Feature.Enabled.NodePath
// "Feature.Enabled"

You can customize the separator:

[JsonConstGenerator("constants.json", Seperator = "/")]
public static partial class MyConstants
{
}

Result:

// "Feature/Enabled"

Requirements

  • The target class must be partial
  • The class is typically declared as static
  • JSON files must be included as AdditionalFiles

Node Types

The generator produces different node types depending on the JSON value.

JSON value Generated node
integer number ValueConstNode<int>
large integer (outside Int32) ValueConstNode<long>
number containing . ValueConstNode<double>
string ValueConstNode<string>
arrays ValueConstNode<object[]>
empty object ValueLessConstNode
null ValueLessConstNode
empty array ValueLessConstNode

Array Handling

Arrays with a consistent type are generated as arrays of that type. If the array contains multiple types, it is generated as an object[].

Arrays of strings are generated as a class that is both a IParentConstNode and a IValueConstNode<string[]>, enabling both the usability of the array while also making arrays a easy implementation for many ValueLessConstNodes.

Example JSON:

{
  "Permissions": ["Read", "Write", "Delete"]
}

Each entry becomes a ValueLessConstNode.

Generated structure:

Permissions = new()
{
    Read = new("Permissions.Read"),
    Write = new("Permissions.Write"),
    Delete = new("Permissions.Delete")
}

GeneratedClass:

public class PermissionsConstNode : IParentConstNode, IValueConstNode<string[]>
{
    public ValueLessConstNode Read {get; init;}
    public ValueLessConstNode Write {get; init;}
    public ValueLessConstNode Delete {get; init;}

    public const string NodePath = "Permissions";
    public const string[] NodeValue = new [] { "Permissions.Read", "Permissions.Write", "Permissions.Delete" };
}

Accessing Nodes

Nodes can be accessed directly through the generated tree.

var node = MyConstants.Parent1.Group1.Child1;
var value = node.NodeValue;
var path = node.NodePath;

Root Helper Methods

The generated root class contains helper methods for working with nodes without reflection.

Get all nodes

foreach (var node in MyConstants.GetAllNodes())
{
    Console.WriteLine(node.NodePath);
}

Get all end nodes

End nodes are nodes that do not contain any child nodes.

foreach (var node in MyConstants.GetAllEndNodes())
{
    Console.WriteLine(node.NodePath);
}

Find a node by path

The FindNode function is used to find a node based on its string value, throws an exception if the provided node does not exist. The TryFindNode functions is a safer way to get a node without getting a exception

var node = MyConstants.FindNode("Parent1.Group1.Child1");

if(MyConstants.TryFindNode("Parent1.Group1.Child1", out var node2))
{
}

Get a node value directly

This function can be used on value nodes where you know the expected value type. This method throws a exception if the node is not found or the valuetype cannot be converted.

bool isEnabled = MyConstants.GetNodeValue<bool>(featureFlagKey);

Node Classes

Interfaces

IConstNode

This is the basic interface that is applied on all node classes. All implementations of the IConstNode also have the ToString() method overwritten to return the NodePath

/// <summary>
/// The base interface that is applied on all the const nodes
/// </summary>
public interface IConstNode
{
    /// <summary>
    /// Get the absolute path of this node
    /// </summary>
    public string NodePath { get; }
}

IValueConstNode

/// <summary>
/// A value node is a <see cref="IConstNode"/> that also contains a value
/// </summary>
interface IValueConstNode : IConstNode
{
    /// <summary>
    /// The current value of this node
    /// </summary>
    public object NodeValue { get; }
}

IParentConstNode

/// <summary>
/// A parent node that contains child nodes
/// </summary>
public interface IParentConstNode : IConstNode
{
    /// <summary>
    /// Returns all the child nodes of this parent
    /// </summary>
    /// <returns></returns>
    public IEnumerable<IConstNode> GetChildNodes();


    /// <summary>
    /// Returns all the child nodes of this parent, but also include sub children
    /// </summary>
    /// <returns></returns>
    public IEnumerable<IConstNode> GetChildNodesRecursive();
}

Structs

ValueLessConstNode

A valueless node can implicitly convert to a string representing its path. Since this is likely the only available property

/// <summary>
/// A endnode which does not contain a value
/// </summary>
public struct ValueLessConstNode : IConstNode
{
    public ValueLessConstNode(string path)
    {
        NodePath = path;
    }
    public string NodePath { get; }

    public override string ToString() => NodePath;
    public static implicit operator string(ValueLessConstNode node) => node.NodePath;
}

ValueConstNode<T>

A ConstNode which also contains a value. A ConstNode can implicitly parse the value, while providing the NodePath with .ToString()

/// <summary>
/// A const node which contains a value
/// </summary>
/// <typeparam name="T"></typeparam>
public struct ValueConstNode<T> : IValueConstNode, IConstNode
{
    public ValueConstNode(string path, T value)
    {
        NodePath = path;
        NodeValue = value;
    }
    public T NodeValue { get; }

    public string NodePath { get; }

    object IValueConstNode.NodeValue => NodeValue;

    public override string ToString() => NodePath;
    public static implicit operator T(ValueConstNode<T> node) => node.NodeValue;
}

Parent Nodes

Parent nodes implement the [IParentConstNode] and are generated at compile time for each object required, but can be reused if multiple objects contain the exact same properties. The .ToString() is overwritten to return the NodePath of this parentnode.


Requirements

  • .NET SDK supporting C# Source Generators
  • JSON files included as AdditionalFiles

License

MIT License

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 110 7/11/2026