The JSON clearly represents a token tree (possibly produced by a parser).
Visitor pattern use polymorphism.
In order to be used by a Visitor pattern, you must deserialize it to obtain objects with the different Visit behavior :
- MethodToken
- OperatorToken
- NameToken
Then IVisitor should implement Visit method for each:
public interface IVisitor
{
void Visit(MethodToken token) { /* */ }
void Visit(OperatorToken token) { /* */ }
void Visit(NameToken token) { /* */ }
}
public interface IVisitable
{
void Accept(IVisitor visitor);
}
public class MethodToken : IVisitable
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
Additional remark:
Object
is a really poor name especially in C# as Object
is the base class for every classes, not to mention the conflict, it doesn't convey any special meaning ... What about token ?
public class Token
{
public string Name { get; set; }
public string Value { get; set; }
public List<Token> Children { get; set; }
}
About property Childs...
Purpose of Visitor
You shouldn't use a screwdriver if you don't know when/why to use it (by the way it can be dangerous).
Visitor pattern is useful to avoid 'ugly'/hard to maintain/painful to read dozen switch cases or the even worse if else if else
while giving you the strong type checking advantage. It also helps to keep related code (high cohesion) in one class (the Visitor). Of course, once implemented, the tree of objects (here tokens) can be visited by several kind of visitors as long as they implement the IVisitor
interface.
In your case, you must first convert each Token
to a strongly subtype of Token
(through Dictionary mapping to avoid any if/switch or custom deserialization)
In your case:
- First read the text (obviously it's json format) and transform it to an object. We usually call this deserialization. It's possible here because the text is already formatted with a well-known correct structured format for which it is easy to find a lexer/parser. (Otherwise you would have to write your own lexer/parser or use something like lex/yacc).
However, we have to partial deserialize each part of the text to the right type.
We will use Newtonsoft.Json
to do this:
// We define a base class abstract (it cannot be instantiated and we can enforce implementation of methods like the Accept()
public abstract class BaseToken : IVisitable
{
public string Value { get; set; }
public List<BaseToken> Children { get; } = new List<BaseToken>();
public abstract void Accept(IVisitor visitor);
}
Read the text and parse Json:
// Load text in memory
var text = File.ReadAllText("path/to/my/file.json");
// Get Token instance
var jsonToken = JObject.Parse(text);
- We must process
JToken
to extract the right class instances:
// Get the strong typed tree of token
var token = CreateToken(jsonToken);
CreateToken
method:
private static BaseToken CreateToken(JToken jsonToken)
{
var typeOfToken = jsonToken["Name"];
if (typeOfToken == null || typeOfToken.Type != JTokenType.String)
{
return null;
}
BaseToken result;
switch (typeOfToken.ToString())
{
case "Method":
{
result = jsonToken.ToObject<MethodToken>();
break;
}
case "Operator":
{
result = jsonToken.ToObject<OperatorToken>();
break;
}
default:
{
result = jsonToken.ToObject<NameToken>();
break;
}
}
var jChildrenToken = jsonToken["Childs"];
if (result != null &&
jChildrenToken != null &&
jChildrenToken.Type == JTokenType.Array)
{
var children = jChildrenToken.AsJEnumerable();
foreach (var child in children)
{
var childToken = CreateToken(child);
if (childToken != null)
{
result.Children.Add(childToken);
}
}
}
return result;
}
As you can see there are still some switch pattern on text.
- Then call the token visitor:
// Create the visitor
var tokenVisitor = new TokenVisitor();
// Visit the tree with visitor
token.Accept(tokenVisitor);
// Output the result
Console.WriteLine(tokenVisitor.Output);
Code of TokenVisitor
internal class TokenVisitor : IVisitor
{
private readonly StringBuilder _builder = new StringBuilder();
// invert the order of children first
private int firstIndex = 1;
private int secondIndex = 0;
// Keep track of name tokens
private readonly HashSet<BaseToken> _visitedTokens = new HashSet<BaseToken>();
public string Output => _builder.ToString();
public void Visit(MethodToken token)
{
// Store local to avoid recursive call;
var localFirst = firstIndex;
var localSecond = secondIndex;
// back to normal order of children
firstIndex = 0;
secondIndex = 1;
RenderChild(token.Children, localFirst);
_builder.Append(token.Value);
RenderChild(token.Children, localSecond);
}
private void RenderChild(List<BaseToken> children, int index)
{
if (children.Count > index)
{
_builder.Append("(");
children[index].Accept(this);
_builder.Append(")");
}
}
public void Visit(OperatorToken token)
{
if (token.Children.Count > 0)
{
token.Children[0].Accept(this);
_builder.Append(" ");
}
_builder.Append(token.Value);
if (token.Children.Count > 0)
{
_builder.Append(" ");
token.Children[0].Accept(this);
}
}
public void Visit(NameToken token)
{
if (_visitedTokens.Contains(token))
{
_builder.Append(token.Value);
}
else
{
_visitedTokens.Add(token);
_builder.Append(token.Name);
}
}
}
The above implementation seeks to cope with your expectations(ie output exactly the expected string). It may not be bulletproof. You can find the full code on GitHub
Expression Trees
msdn.microsoft.com/en-us/library/bb397951.aspx ? By the way,Object
is not a good class name. – Windtight