1

I have an object which is being returned as a payload of content for another via an API.

Some of the properties of the object (which is complex and can contain lists of other objects, lists of different types, etc.) can contain placeholder values which I need to swap for the correct values before the object is returned.

Currently, to keep it simple, I am serializing the object to a string, running a custom replace function on said string to replace all placeholder instances, and then I am deserializing the string back to an object so it's in the correct format for the site that's going to consume it. This works, and as I am generally able to get a response from the API in less than 10ms, I am not worried about performance loss from serializing/deserializing the object to achieve my goal.

However, I have since found that some of the placeholders may be nested within others, so my approach no longer holds up. I have found that using the LINQ Aggregate() function, nested placeholder values are correctly replaced, and it's still performant:

public string ReplacePlaceholderValues(string source, Dictionary<string, string> placeholders)
{
    var replacedText = placeholders.Aggregate(source, (current, replacement) =>
    {
        return current.Replace(replacement.Key, replacement.Value);
    });
    return replacedText;
}

My issue comes when I need to deserialize the string back to an object. If a nested placeholder contains say double-quotes, JsonConvert.Deserialize breaks with "unexpected character was encountered", which is expected. I can change the above method:

public string ReplacePlaceholderValues(string source, Dictionary<string, string> placeholders)
{
    var replacedText = placeholders.Aggregate(source, (current, replacement) =>
    {
        return current.Replace(replacement.Key, JsonConvert.SerializeObject(replacement.Value(replacement.Value));
    });
    return replacedText;
}

but this will wrap all my strings in double-quotes, which is also no good.

Can anyone suggest a way around this to fix the formatting of nested placeholders (preferable solution).

I am open to suggestions for changing how this works, I have toyed with the idea of reflection to do a string replace on all string properties on the initial object, forgoing serialization/deserialization completely, but this could be quite complicated (due to nested classes, lists of nested classes, etc.) and will need reflection as the object could be quite complex and I want this to be a pretty generic solution where different objects can be manipulated, hence going down the route of serializing the object first.

Here is a fiddle of essentially what is happening. My page payload object is more complex etc. etc.:

https://dotnetfiddle.net/eKHxwn

(Feel free to point out any obvious issues, beyond the primary one, I am likely suffering with tunnel vision...)

Mark Johnson
  • 445
  • 1
  • 6
  • 11
  • You might be able to do this with a custom `IValueProvider`. See e.g. [Overriding a property value in custom JSON.net contract resolver](https://stackoverflow.com/q/46977905/3744182) or [NewtonSoft JsonConverter - Access other properties](https://stackoverflow.com/a/47872645/3744182). Can you share a [mcve] showing what you need to do? – dbc Dec 03 '20 at 17:42
  • Or you could [serialize your data model to a `JToken` hierarchy](https://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Linq_JToken_FromObject.htm) and replace all appropriate `JValue.Value` values therein. We need a more details to suggest the most appropriate solution. – dbc Dec 03 '20 at 17:53
  • @dbc, I have edited the post and added a fiddle as requested. I haven't dealt with JToken before, but as Brian has suggested in an answer I will take a look at that as it sounds promising. – Mark Johnson Dec 04 '20 at 10:24

1 Answers1

3

I would suggest loading your object into a JToken hierarchy and then replacing the values from there. This will allow you to do the string replacements without having to worry about escaping problems. You can use the Descendants method to traverse the hierarchy with a simple foreach loop and then use your existing ReplacePlaceholderValues method from there.

First, define a new overload for ReplacePlaceholderValues which accepts a JContainer (a JContainer is the base class for JObject or JArray):

public static void ReplacePlaceholderValues(JContainer root, Dictionary<string, string> placeholders)
{
    var jValues = root.Descendants()
                      .OfType<JValue>()
                      .Where(jv => jv.Type == JTokenType.String);

    foreach (var jVal in jValues)
    {
        jVal.Value = ReplacePlaceholderValues((string)jVal.Value, placeholders);
    }
}

Then, to use the method, you need to get your data into a JContainer. If you are starting from an object or array, use JToken.FromObject():

var root = (JContainer)JToken.FromObject(payloadObject);

If your payload is already a JSON string, then use JToken.Parse() instead:

var root = (JContainer)JToken.Parse(jsonString);

Next call the new ReplacePlaceholderValues method on the JContainer. It will update all the replaceable values in place:

ReplacePlaceholderValues(root, placeholders);

Finally, convert the JContainer to an object or JSON using ToObject<T>() or ToString(), respectively, as you require:

payloadObject = root.ToObject<TypeOfYourPayloadObject>();

OR

jsonString = root.ToString();

Here is a working demo: https://dotnetfiddle.net/IyRAmz

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300