C# Logical and Math expression parser I needed a simple parser that could do both logical and math operations expressed in a string, as well as being able to use variables stored in json.
None of what I found online seemed to do all of the above, so I came up with my own. Below is the source code for my ExpressionParser as well as my JExpressionParser, in case your variables are stored in a JSON.
Hopefully this post will help someone, as well as allow others to improve my code.
Here is the base parser:
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;

public class ExpressionParser
{
    public virtual IDictionary&lt;string, object&gt; Variables { get; }
    protected ExpressionParser() { }
    protected virtual DataTable GetComputer()
    {
        var computer = new DataTable();
        if (Variables != null)
        {
            computer.Columns.AddRange(Variables.Select(v =&gt; new DataColumn(v.Key) { DataType = v.Value.GetType() }).ToArray());
            computer.Rows.Add(Variables.Values.ToArray());
        }
        return computer;
    }
    public ExpressionParser(IDictionary&lt;string, object&gt; variables = null)
    {
        Variables = variables ?? new Dictionary&lt;string, object&gt;();
    }
    public object Compute(string expression)
    {
        StringBuilder sb = new StringBuilder(expression);
        foreach (var key in Variables.Keys)
            sb.Replace(key, $&quot;Sum({key})&quot;);
        sb.Replace(&quot;==&quot;, &quot;=&quot;);
        using (var computer = GetComputer())
            return computer.Compute(sb.ToString(), null);
    }
}

And here is one that works with json objects:
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;

public class JExpressionParser : ExpressionParser
{
    public JObject JVariables { get; set; }
    public override IDictionary&lt;string, object&gt; Variables =&gt; 
        JVariables.Properties().ToDictionary(p =&gt; p.Name, p =&gt; p.Value.ToObject(conversions[p.Value.Type]));
    static readonly Dictionary&lt;JTokenType, Type&gt; conversions = new Dictionary&lt;JTokenType, Type&gt;()
    {
        [JTokenType.Integer] = typeof(int),
        [JTokenType.Float] = typeof(float),
        [JTokenType.Boolean] = typeof(bool),
    };
    public JExpressionParser(JObject jVariables = null)
    {
        JVariables = jVariables ?? new JObject();
    }
}

Usage:
var variables = new Dictionary&lt;string, object&gt;() { [&quot;budget&quot;] = 1000, [&quot;cost&quot;] = 965 };
var parser = new ExpressionParser(variables);
Console.WriteLine(&quot;We have $&quot; + parser.Compute(&quot;budget - cost&quot;) + &quot; of budget left.&quot;);
I can see three small improvement areas:
Use ctor instead of object initializer
Instead of this:
new DataColumn(v.Key) { DataType = v.Value.GetType() }

Use this:
new DataColumn(v.Key, v.Value.GetType())

Object initializer will run after ctor. If ctor accepts that parameter then prefer to provide it in the ctor.
Use early exist instead of guard expression
Instead of this:
if (Variables != null)
{
    computer.Columns.AddRange(Variables.Select(v =&gt; new DataColumn(v.Key, v.Value.GetType())).ToArray());
    computer.Rows.Add(Variables.Values.ToArray());
}
return computer;

Use this:
if ((Variables?.Any() ?? true)) return computer;

computer.Columns.AddRange(Variables.Select(v =&gt; new DataColumn(v.Key, v.Value.GetType())).ToArray());
computer.Rows.Add(Variables.Values.ToArray());
return computer;

Yes, I know it's against the single return statement. But with this approach you streamline your logic.
Use immutable collection instead of just readonly access modifier
Instead of this:
static readonly Dictionary&lt;JTokenType, Type&gt; conversions = new Dictionary&lt;JTokenType, Type&gt;()
{
    [JTokenType.Integer] = typeof(int),
    [JTokenType.Float] = typeof(float),
    [JTokenType.Boolean] = typeof(bool),
};

Use this:
static readonly ImmutableDictionary&lt;JTokenType, Type&gt; conversions = new Dictionary&lt;JTokenType, Type&gt;()
{
    [JTokenType.Integer] = typeof(int),
    [JTokenType.Float] = typeof(float),
    [JTokenType.Boolean] = typeof(bool),
}.ToImmutableDictionary();

With readonly you only prevent to replace the whole object. But you can still add and remove new items to the dictionary:
//Invalid operations
conversions = null;
conversions = new Dictionary&lt;string, JTokenType&gt; {};

//Valid operations
conversions.Add(JTokenType.Array, typeof(Array));
conversions.Remove(JTokenType.Integer);

With ImmutableDictionary the Add and Remove will return a new ImmutableDictionary so it will not have any effect on the original collection. Visual Studio will even worn you about it: