Jeff
Jeff

Reputation: 36573

JSON .NET Custom Symbol Resolver

I'd like to use variables when deserializing some JSON. For example, if I have the JSON string (which I know isn't really valid JSON):

{
   "foo": bar
}

I want the opportunity to resolve the symbol bar

So, at deserialization time, I want to end up with the equivalent of the following being deserialized:

{
  "foo": { "baz": "foobar" }
}

Is this possible using JSON .NET? Right now I just get:

Additional information: Unexpected character encountered while parsing value: bar. Path 'foo', line 1, position 9.

Upvotes: 1

Views: 64

Answers (1)

Brian Rogers
Brian Rogers

Reputation: 129777

You could make a helper method to do this. The helper method could first deserialize everything to a JToken hierarchy, then recursively search for and replace the variables using a replacement function you supply. After the replacement, it could then use JToken.ToObject() to hydrate it into your target class(es). The only catch in the plan is that the original JSON has to be valid. So, I would propose using specially-formatted string values for your variables instead, such that there is no chance of one getting confused with a normal string value. Perhaps something like this:

{
    "foo": "$(bar)"
}

Here is what the code for the helper might look like:

public static class JsonHelper
{
    public static T DeserializeAndReplace<T>(string json, Func<string, object> replaceFunc)
    {
        return ReplaceVariables(JToken.Parse(json), replaceFunc).ToObject<T>();
    }

    public static JToken ReplaceVariables(JToken token, Func<string, object> replaceFunc)
    {
        if (token.Type == JTokenType.Object)
        {
            JObject copy = new JObject();
            foreach (JProperty prop in token.Children<JProperty>())
            {
                copy.Add(prop.Name, ReplaceVariables(prop.Value, replaceFunc));
            }
            return copy;
        }
        if (token.Type == JTokenType.Array)
        {
            JArray copy = new JArray();
            foreach (JToken item in token.Children())
            {
                copy.Add(ReplaceVariables(item, replaceFunc));
            }
            return copy;
        }
        if (token.Type == JTokenType.String)
        {
            string s = (string)token;
            if (s.StartsWith("$(") && s.EndsWith(")"))
            {
                object value = replaceFunc(s.Substring(2, s.Length - 3));
                return (value != null ? JToken.FromObject(value) : JValue.CreateNull());
            }
        }
        return token;
    }
}

Here is a demo showing the helper in action:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        [
            {
                ""name"" : ""normal"",
                ""bar"" : { ""baz"" : ""quux"" }
            },
            {
                ""name"" : ""$(cool)"",
                ""bar"" : ""$(bar)""
            },
        ]";

        var list = JsonHelper.DeserializeAndReplace<List<Foo>>(json, ReplaceVariable);

        foreach (Foo foo in list)
        {
            Console.WriteLine("name: " + foo.Name);
            Console.WriteLine("bar.baz: " + foo.Bar.Baz);
            Console.WriteLine();
        }
    }

    private static object ReplaceVariable(string variable)
    {
        if (variable == "bar") return new Bar { Baz = "foobar" };
        if (variable == "cool") return "whip";
        return null;
    }
}

class Foo
{
    public string Name { get; set; }
    public Bar Bar { get; set; }
}

class Bar
{
    public string Baz { get; set; }
}

Output:

name: normal
bar.baz: quux

name: whip
bar.baz: foobar

Fiddle: https://dotnetfiddle.net/5T9lmd

Upvotes: 1

Related Questions