How to parse non-key JSON to C# classes? - c#

I get a Json data from url as follows. But as you see, there are no key names in Json.
For example, "Flame Towers" is place name value but there is no any key name. Likewise, "2017-02-10" is date value, "The Lego Batman Movie 2D" is film name value but it declared as key and ["10:10"] is an array consists film session times.
I tried many class structurs for deserialize it to C# classes using JsonConvert.DeserializeObject<ClassName>(jsonString);
But every time it returns a null object. Also tried parse manually with JObject class and it seemed to me very confused.
So, can anyone help for true class structure parsing with JsonConvert class?
{
{
"Flame Towers": {
"2017-02-10": {
"The Lego Batman Movie 2D": [
"10:10"
],
"Qatil 2D": [
"10:30"
],
"Fifty Shades Darker 2D": [
"10:30",
"11:40",
"12:50",
"14:00",
"15:10",
"16:20",
"17:30",
"18:40",
"19:50",
"21:00",
"22:10",
"23:20",
"00:30",
"01:40"
],
"John Wick: Chapter Two 2D": [
"11:00",
"12:10",
"13:20",
"14:30",
"15:40",
"16:50",
"18:00",
"20:20",
"21:30",
"22:40",
"23:50",
"01:00",
"02:10"
],
"The Lego Batman Movie 3D": [
"11:00",
"12:10",
"13:00",
"14:10",
"15:00",
"17:00",
"19:00"
],
"Ballerina 3D": [
"16:10"
],
"Rings 2D": [
"17:55"
],
"Ağanatiq 2D": [
"19:55"
],
"Resident Evil: The Final Chapter 3D": [
"21:40",
"21:00",
"23:50",
"01:10"
],
"The Great Wall 3D": [
"23:10"
]
}
},
"Metro Park": {
"2017-02-10": {
"John Wick: Chapter Two 2D": [
"10:30",
"12:50",
"15:10",
"17:30",
"19:50",
"22:10",
"00:30"
],
"Ağanatiq 2D": [
"10:00",
"11:50",
"13:40",
"15:30",
"17:20",
"19:10",
"21:00",
"23:00",
"00:50"
],
"The Lego Batman Movie 2D": [
"10:30"
],
"Fifty Shades Darker 2D": [
"11:00",
"13:20",
"15:40",
"18:00",
"20:20",
"02:00"
],
"Hoqqa 2D": [
"11:10",
"12:50",
"14:30",
"16:10",
"17:50",
"19:30",
"21:10",
"22:50",
"00:30",
"02:10"
],
"Naxox 2D": [
"11:20",
"13:10",
"15:00",
"16:50",
"18:40",
"20:30",
"22:20",
"00:10"
],
"The Lego Batman Movie 3D": [
"12:30",
"14:30",
"16:30",
"18:30"
],
"Ballerina 3D": [
"20:30"
],
"Resident Evil: The Final Chapter 3D": [
"22:40",
"00:50"
],
"The Great Wall 3D": [
"22:20",
"02:30"
],
"Притяжение 3D": [
"00:20"
]
}
}
}
}

There is a simple, hacky and speedy way of doing this. Just cut the first and the last { } symbols from the string before serializing.
if (jsonString.StartsWith("{{") && jsonString.EndsWith("}}"))
jsonString = jsonString.Substring(2, jsonString.Length - 4);
JsonConvert.DeserializeObject<ClassName>(jsonString);

It looks like data from a collection of movie theatres and their active shows where the top item "Flame Towers" are the name of the cinema, the "2017-02-10" is the date and under is each show/movie and then their "display" time.
Knowing this, you can create a data structure that matches this.
... Something like this perhaps?
public class Movie : IEnumerable<TimeSpan>
{
public Movie(string name, IReadOnlyList<TimeSpan> runTimes)
{
this.Name = name;
this.RunTimes = runTimes;
}
public string Name { get; }
public IReadOnlyList<TimeSpan> RunTimes { get; }
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<TimeSpan> GetEnumerator()
{
return RunTimes.GetEnumerator();
}
public override string ToString()
{
return "[Movie] " + Name;
}
public static Movie Parse(JProperty data)
{
var name = data.Name;
var runTimes = new List<TimeSpan>();
foreach (var child in data.Values())
{
runTimes.Add(TimeSpan.Parse(child.Value<string>()));
}
return new Movie(name, runTimes);
}
}
public class MovieCollectionDate : IEnumerable<Movie>
{
public MovieCollectionDate(DateTime date, IReadOnlyList<Movie> movies)
{
this.Date = date;
this.Movies = movies;
}
public DateTime Date { get; }
public IReadOnlyList<Movie> Movies { get; }
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<Movie> GetEnumerator()
{
return this.Movies.GetEnumerator();
}
public override string ToString()
{
return "[Date] " + Date + " - " + Movies.Count + " show(s)";
}
public static MovieCollectionDate Parse(JProperty data)
{
var date = DateTime.Parse(data.Name);
var movies = new List<Movie>();
foreach (var upperChild in data.Children<JObject>())
{
foreach (var child in upperChild.Children())
{
movies.Add(Movie.Parse(child as JProperty));
}
}
return new MovieCollectionDate(date, movies);
}
}
public class MovieTheatre : IEnumerable<MovieCollectionDate>
{
public MovieTheatre(string name, IReadOnlyList<MovieCollectionDate> dateAndMovies)
{
this.Name = name;
this.DateAndMovies = dateAndMovies;
}
public string Name { get; }
public IReadOnlyList<MovieCollectionDate> DateAndMovies { get; }
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<MovieCollectionDate> GetEnumerator()
{
return this.DateAndMovies.GetEnumerator();
}
public override string ToString()
{
return "[Theatre] " + Name + " - " + DateAndMovies.Count + " open day(s)";
}
public static MovieTheatre Parse(JProperty data)
{
var name = data.Name;
var movieCollectionDates = new List<MovieCollectionDate>();
foreach (var upperChild in data.Children<JObject>())
{
foreach (var child in upperChild.Children())
{
movieCollectionDates.Add(MovieCollectionDate.Parse(child as JProperty));
}
}
return new MovieTheatre(name, movieCollectionDates);
}
}
public class MovieTheatreCollection : IEnumerable<MovieTheatre>
{
public MovieTheatreCollection(IReadOnlyList<MovieTheatre> movieTheatres)
{
this.MovieTheatres = movieTheatres;
}
public IReadOnlyList<MovieTheatre> MovieTheatres { get; }
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<MovieTheatre> GetEnumerator()
{
return this.MovieTheatres.GetEnumerator();
}
public override string ToString()
{
return "MovieTheatreCollection: Containing " + MovieTheatres.Count + " movie theatre(s)";
}
public static MovieTheatreCollection Parse(JObject data)
{
var theatres = new List<MovieTheatre>();
foreach (var child in data.Children().Cast<JProperty>())
{
theatres.Add(MovieTheatre.Parse(child));
}
return new MovieTheatreCollection(theatres);
}
}
this is obviously not the most elegant way of solving the problem. But seeing as this "key-less" json wouldnt just Deserialize properly without some sort of hack. Creating a data structure that matches your needs (more manual work unfortunately) will at least work ;)
You can use the code above with the following code:
JObject obj = JObject.Parse(... the json string you had above ...)
MovieTheatreCollection movieTheatres = MovieTheatreCollection.Parse(obj);
foreach (var movieTheatre in movieTheatres)
{
Console.WriteLine(movieTheatre);
foreach (var openDay in movieTheatre)
{
Console.WriteLine(" " + openDay);
foreach (var movie in openDay)
{
Console.WriteLine(" " + movie);
foreach (var runtime in movie) Console.WriteLine(" - " + runtime);
}
}
}
Finally, just like 'Just Shadow' mentioned in the answer above, the json is malformed and contains extra curly brackets that needs to be removed or the object wont parse properly.

An ugly, but reasonably compact way to parse this would be:
static void Main(string[] args)
{
var jo = JObject.Parse(File.ReadAllText("data.json").Trim('{').Trim('}'));
foreach (var place in jo)
{
Console.WriteLine($"Place: {place.Key}");
foreach (var dateOrMovie in place.Value.Children<JProperty>())
{
Console.WriteLine($"\tDate: {dateOrMovie.Name}");
var movies = dateOrMovie.Children<JObject>().First().Children<JProperty>();
foreach (var movie in movies)
{
Console.WriteLine($"\t\t{movie.Name}");
foreach (JValue time in movie.Children<JArray>().First())
{
Console.WriteLine($"\t\t\t{time.Value}");
}
}
}
}
}

Related

Is HashSet of a HashSet<T> the right data model here?

I'm managing a data structure that looks like the following
[
{
"name": "Yada yada",
"dataModels": [
{
"entity": "Table",
"columns": [
[
{
"column": "ColumnA",
"value": " \"StringA\""
},
{
"column": "ColumnB",
"value": " \"StringB\""
}
],
...,
[
{
"column": "ColumnA",
"value": " \"StringA\""
},
{
"column": "ColumnB",
"value": " \"StringB\""
}
],
...
]
}
]
}
]
Every object in the columns list is a HashSet of ColumnValues, a class I created and is defined the following way:
public class ColumnValues
{
private string column;
private string value;
public ColumnValues(string col, string val)
{
column = col;
value = val;
}
public string Column
{
get {return column;}
}
public string Value
{
get {return value;}
}
public override bool Equals(object obj)
{
return obj is ColumnValues values &&
column == values.column &&
value == values.value &&
Column == values.Column &&
Value == values.Value;
}
public override int GetHashCode()
{
return HashCode.Combine(column, value, Column, Value);
}
}
So far, so good. However the issue is that in columns, I'm having duplicates of lists. columns is a field in DataModel, another class:
public class DataModel
{
private string entity;
private List<HashSet<ColumnValues>> columns;
private string rule;
public DataModel(string entityName, List<HashSet<ColumnValues>> columnList)
{
entity = entityName;
columns = columnList;
}
public string Entity
{
get { return entity; }
}
public List<HashSet<ColumnValues>> Columns
{
get { return columns; }
set { columns = value; }
}
public string Rule
{
set { rule = value; }
}
}
I'm not understanding why the Set is allowing the existence of duplicates.
I do add that I'm returning the columns list after applying Distinct().ToList():
var dataModels = new List<DataModel>();
GatherColumns(code.Syntax);
dataModels.ForEach(dataModel => dataModel.Columns = dataModel.Columns.Distinct().ToList());
return dataModels;
TIA!
EDIT:
As per Tim's comment, I've provided the override methods Equals and GetHashCode.
I've also decided to define Columns in DataModel as a List of HashSets.
However, unfortunately the result hasn't changed.
EDIT N°2:
I've decided to create a class implementing the IEqualityComparer interface. However, little effect so far:
public class ColumnValuesComparer : IEqualityComparer<HashSet<ColumnValues>>
{
public bool Equals(HashSet<ColumnValues> c1, HashSet<ColumnValues> c2)
{
return c1.Equals(c2);
}
public int GetHashCode(HashSet<ColumnValues> obj)
{
return obj.GetHashCode();
}
}
Used here:
var dataModels = new List<DataModel>();
GatherColumns(code.Syntax);
ColumnValuesComparer comparer = new ColumnValuesComparer();
dataModels.ForEach(dataModel => dataModel.Columns = dataModel.Columns.Distinct(comparer).ToList());
return dataModels;
At this point I'm pretty much stuck, and don't know where to go moving forward.
After a bit of searching a documentation, I fell upon the concept of SetComparer.
They allow me to create objects that evaluate deep nested equality within HashSets.
So, when I try to create my HashSet of HashSets, I must pass that method:
var set = new HashSet<HashSet<ColumnValues>>(HashSet<ColumnValues>.CreateSetComparer());
set.Add(columnValues);

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.ICollection`1[System.Double]'

As i post above, i can't make it through.
Im not sure how should like implementation of this, because i'm pretty new at front-end. At first i post example back-end JSON file (i have problem with borderPoints variable, before i add it, everything works fine).
{
city: "Warszawa",
id: "b7e476a5-5894-4510-9630-637ac3a2191d",
name: "Test",
parcels: [{
id: "b7e476a5-5894-4510-9630-637ac3a2191d",
latitude: 0,
longitude: 0,
"borderCoordinates": [
[
0,
0
],
[
1,
1
],
[
1,
0
]
], }], }
I post bellow some important parts of code.
land.model.ts:
...
export class LocationPoint {
public latitude: number;
public longitude: number;
}
export class LocationPointDto {
public latitude: number;
public longitude: number;
}
export class Land {
...
public id: Guid;
public borderCoordinates: LocationPoint[];
}
export class LandDto {
...
public id: string;
public borderCoordinates: LocationPoint[];
constructor(
...
id: string,
borderCoordinates: LocationPoint[]
) {
...
this.id = id;
this.borderCoordinates = borderCoordinates;
}
}
...
land.module.ts
...
export function initializeMappings(mappingService: MappingService): any {
return (): void => {
mappingService.addMapping(LocationPoint, LocationPointDto, new BorderCoordinatesMapper());
mappingService.addMapping(Land, LandDto, new LandMapper());
...
land-builder.ts
...
public withborderCoordinates(borderCoordinates: LocationPoint[]): LandBuilder {
this.instance.borderCoordinates = borderCoordinates;
return this;
}
...
land.mapper.ts
...
export class BorderCoordinatesMapper implements Mapper<LocationPoint, LocationPointDto> {
public forwardTransform(locationPoint: LocationPoint): LocationPointDto {
return {
latitude: locationPoint.latitude,
longitude: locationPoint.longitude
};
}
public backwardTransform(locationPointDto: LocationPointDto): LocationPoint {
return {
latitude: locationPointDto.latitude,
longitude: locationPointDto.longitude
};
}
}
export class LandMapper implements Mapper<Land, LandDto> {
public forwardTransform(land: Land, mappingService: MappingService): LandDto {
return {
id: land.id.toString(),
borderCoordinates: { [key: string]: land.borderCoordinates}
? land.borderCoordinates.map(p => mappingService.map(LocationPoint, LocationPointDto, p))
: []
};
}
public backwardTransform(land: LandDto, mappingService: MappingService): Land {
return {
id: Guid.parse(land.id),
borderCoordinates: land.borderCoordinates
? land.borderCoordinates.map(p => mappingService.map(LocationPointDto, LocationPoint, p))
: []
};
}
}
...
Try this in your json:
"borderCoordinates": [
{
"latitude": 0,
"longitude"0
},
{
"latitude": 1,
"longitude": 1
},
{
"latitude": 1,
"longitude": 0
}
]
I still didn't found a solution for my problem ... that's sad.

Deserializing JObject that has no property name

I want to be able to print the type, name and the sub entries, but I'm at a a loss as to how. Can I access specific positions of an array instead of naming the property I want?
The main property has entry properties, like mainProperty.entries, and I can pull the strings out using a foreach. But when the "entries" has properties that don't have a name, I have no idea how to access them.
I know that the "Entry text here" are JValues, and that the others are JObjects.
The entry without objects can be accessed through mainProperty.entries.
Since the other type doesn't have a name, I don't know how to access the type, name and "sub" entries. (mainProperty.JObject.type)
{
"mainProperty": [
{
"entries": [
"Entry text here",
{
"type": "entries",
"name": "Entry of entry",
"entries": [
"Entry text here"
]
},
{
"type": "entries",
"name": "Entry of entry",
"entries": [
"Entry text here"
]
}
]
}
]
}
{
"mainProperty": [
{
"entries": [
"Entry text here",
"Second line of text"
]
}
]
}
foreach (var entry in mainProperty.entries)
{
debugOutput(entry.GetType().ToString());
//debugOutput("\t" + (string)entry);
}
The output should be:
First example:
Entry text here
**Entry of entry** Entry text here
**Entry of entry** Entry text here
Second example:
Entry text here
Second line of text
In your JSON, entries is an array of mixed types, where the values can either be strings or objects. If they are objects, each child object has an entries property whose value is also an array. In your examples it seems that this inner array will always contain strings, but it looks like it could actually be a fully recursive structure, where sub-entries can contain sub-sub-entries and so on. If that is so, you would need a recursive method to dump out the entries. Something like this could work:
public static void DumpEntries(JObject obj, string indent = "")
{
JArray entries = (JArray)obj["entries"];
if (entries != null)
{
foreach (JToken entry in entries)
{
if (entry.Type == JTokenType.String)
{
debugOutput(indent + entry.ToString());
}
else if (entry.Type == JTokenType.Object && (string)entry["type"] == "entries")
{
debugOutput(indent + "**" + (string)entry["name"] + "**");
DumpEntries((JObject)entry, indent + " ");
}
}
}
}
Then use it with your JSON like this:
var root = JObject.Parse(json);
foreach (JObject obj in (JArray)root["mainProperty"])
{
DumpEntries(obj);
}
Fiddle: https://dotnetfiddle.net/MjRNGn
In answer to your original question,
Can I access specific positions of an array instead of naming the property I want?
Yes, you absolutely can do this.
For example, if you wanted to get the first sub-entry of the third entry, you could do this:
var root = JObject.Parse(json);
var sub = root["mainProperty"][0]["entries"][2]["entries"][0];
debugOutput((string)sub);
Fiddle: https://dotnetfiddle.net/sMyMxH
If you modify your JSON data like this:
var response = {
"mainProperty": [{
"entries": [{
"type": "entries",
"name": "Entry of entry",
"entries": [
"Entry text here"
]
},
{
"type": "entries",
"name": "Entry of entry",
"entries": [
"Entry text here"
]
}
]
}]
}
You must create Response object but first, you need add NewtonSoft.Json to your project from NuGet. After add this library to your project you need to create Response object like this:
public class Entry
{
public string type { get; set; }
public string name { get; set; }
public List<string> entries { get; set; }
}
public class MainProperty
{
public List<Entry> entries { get; set; }
}
public class RootObject
{
public List<MainProperty> mainProperty { get; set; }
}
Than you can access your data:
var _res = JsonConvert.DeserializeObject<RootObject>(response.ToString());
foreach(var item in _res.mainProperty.entries.entries)
{
debugOutput(item);
}

Get Key / Value of JSON object with Newtonsoft

I see many questions and answers about what I'm trying to do but after reading the answers I'm not able to get the key and value out of this json.
Here is the json being returned:
{
"#odata.context": "https://con813-crm612cf723bbf35af6devaos.cloudax.dynamics.com/data/$metadata#Customers(CustomerAccount,Name)",
"value": [
{
"#odata.etag": "W/\"JzAsMjI1NjU0MjE1NTg7MCwwOzAsNTYzNzE0NTMyODswLDU2MzcxNDQ1NzY7MCwyMjU2NTQyNTY5MzswLDIyNTY1NDI3MjM2OzAsMDswLDIyNTY1NDI3MjM2OzAsMjI1NjU0MjcyMzY7MCwwJw==\"",
"CustomerAccount": "DE-001",
"Name": "Contoso Europe"
},
{
"#odata.etag": "W/\"JzAsMjI1NjU0MjE1NTk7MCwwOzAsMzU2MzcxNDkxMTI7MCw1NjM3MTQ0NTc3OzAsMjI1NjU0MjU2OTQ7MCwyMjU2NTQyNzIzODswLDA7MCwyMjU2NTQyNzIzODswLDIyNTY1NDI3MjM4OzAsMCc=\"",
"CustomerAccount": "US-001",
"Name": "Contoso Retail San Diego"
},
{
"#odata.etag": "W/\"JzAsMjI1NjU0MjE1NjA7MCwwOzAsMzU2MzcxNDkxMTM7MCw1NjM3MTQ0NTc4OzAsMjI1NjU0MjU2OTU7MCwyMjU2NTQyNzI0MDswLDA7MCwyMjU2NTQyNzI0MDswLDIyNTY1NDI3MjQwOzAsMCc=\"",
"CustomerAccount": "US-002",
"Name": "Contoso Retail Los Angeles"
}
]
}
I need to get the names of the keys, which is "CustomerAccount" and "Name" in this example, and then their values. I can't figure out to just return those values.
JObject parsedJson = JObject.Parse(_json);
StringBuilder builder = new StringBuilder();
foreach (JProperty property in parsedJson.Properties())
{
builder.Append((string.Format("Name: [{0}], Value: [{1}].", property.Name, property.Value)));
}
Hoping to add more clarity; In this example I would like to write out the key/values after "#odata.etag" which the keys are "CustomerAccount" and "Name" and their values are after the colon. The keys/values are dynamic so I need to loop through writing out whatever the key names and values are after each "#odata.etag" value.
Use JsonConvert.DeserializeObject and pass the type of object you want to parse. there was a missing closing curly brace at the end. I hope I understood what you want to do correctly, if not plz leave a comment
class Value
{
public string CustomerAccount { get; set; }
public string Name { get; set; }
}
class Customer
{
public List<Value> Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
var obj = JsonConvert.DeserializeObject<Customer>(#" {
'#odata.context': 'https://con813-crm612cf723bbf35af6devaos.cloudax.dynamics.com/data/$metadata#Customers(CustomerAccount,Name)',
'value': [
{
'#odata.etag': 'W/\'JzAsMjI1NjU0MjE1NTg7MCwwOzAsNTYzNzE0NTMyODswLDU2MzcxNDQ1NzY7MCwyMjU2NTQyNTY5MzswLDIyNTY1NDI3MjM2OzAsMDswLDIyNTY1NDI3MjM2OzAsMjI1NjU0MjcyMzY7MCwwJw==\'',
'CustomerAccount': 'DE-001',
'Name': 'Contoso Europe'
},
{
'#odata.etag': 'W/\'JzAsMjI1NjU0MjE1NTk7MCwwOzAsMzU2MzcxNDkxMTI7MCw1NjM3MTQ0NTc3OzAsMjI1NjU0MjU2OTQ7MCwyMjU2NTQyNzIzODswLDA7MCwyMjU2NTQyNzIzODswLDIyNTY1NDI3MjM4OzAsMCc=\'',
'CustomerAccount': 'US-001',
'Name': 'Contoso Retail San Diego'
},
{
'#odata.etag': 'W/\'JzAsMjI1NjU0MjE1NjA7MCwwOzAsMzU2MzcxNDkxMTM7MCw1NjM3MTQ0NTc4OzAsMjI1NjU0MjU2OTU7MCwyMjU2NTQyNzI0MDswLDA7MCwyMjU2NTQyNzI0MDswLDIyNTY1NDI3MjQwOzAsMCc=\'',
'CustomerAccount': 'US-002',
'Name': 'Contoso Retail Los Angeles'
}
]
}");
foreach (var value in obj.Value)
{
Console.WriteLine($"Name: 'Name' Value: {value.Name}");
Console.WriteLine($"Name: 'CustomerAccount' Value: {value.CustomerAccount}");
}
}
}
If you don't know the keys (properties are dynamic) of the object, you could use the following code snippet but it needs a change in the class declaration.
class Customer
{
//this is list of value objects (value is a dictionary)
public List<Dictionary<String,String>> Value { get; set; }
}
And here's how to deserialize and loop through the array of values
var obj = JsonConvert.DeserializeObject<Customer>(myString);
foreach (var value in obj.Value)
{
foreach (var key in value)
{
if (key.Key == "#odata.etag")
continue;
Console.WriteLine("Name: [{0}], Value: [{1}]",key.Key, key.Value);
}
}

Implementing visitor Pattern in C#

I'm new in this pattern , could please someone help me in it?
I got an Object like this :
public class Object
{
public string Name { get; set; }
public object Value { get; set; }
public List<Object> Childs { get; set; }
}
Here is an JSON example:
{
"Name": "Method",
"Value": "And",
"Childs": [{
"Name": "Method",
"Value": "And",
"Childs": [{
"Name": "Operator",
"Value": "IsEqual",
"Childs": [{
"Name": "Name",
"Value": "5",
"Childs": []
}]
},
{
"Name": "Operator",
"Value": "IsEqual",
"Childs": [{
"Name": "Name",
"Value": "6",
"Childs": []
}]
}]
},
{
"Name": "Operator",
"Value": "IsEqual",
"Childs": [{
"Name": "Name",
"Value": "3",
"Childs": []
}]
}]
}
My question how to make Visitor Pattern in order to get this final string:
(Name IsEqual 3)And((Name IsEqul 5)And(Name IsEqual 6))
To implement visitor pattern you need two simple interfaces
IVisitable with an Accept method having the IVisitor as the parameter.
IVisitor with many Visit methods for each implementation of IVisitable
So basic idea of the visitor pattern is to change the behavior dynamically according to the type of implementation.
For your case the thing you want to visit (the visitable) is the Object class which apparently does not have different derivatives and you want to change the behavior according to a property value not the type. So Visitor Pattern is not what you really need here and I highly recommend you to consider the answers with the recursive method.
But if you really want to use visitor pattern here, it may look something like this.
interface IVisitable { void Accept(IVisitor visitor); }
interface IVisitor {
void VisitAnd(Object obj);
void VisitEquals(Object obj);
}
Since the Object class is a simple POCO I assume you won't want to implement an interface and add a method into this class. So you'll need an adapter object which adapts Object to IVisitable
class VisitableObject : IVisitable {
private Object _obj;
public VisitableObject(Object obj) { _obj = obj; }
public void Accept(IVisitor visitor) {
// These ugly if-else are sign that visitor pattern is not right for your model or you need to revise your model.
if (_obj.Name == "Method" && _obj.Value == "And") {
visitor.VisitAnd(obj);
}
else if (_obj.Name == "Method" && _obj.Value == "IsEqual") {
visitor.VisitEquals(obj);
}
else
throw new NotSupportedException();
}
}
}
public static ObjectExt {
public static IVisitable AsVisitable(this Object obj) {
return new VisitableObject(obj);
}
}
And finally the visitor implementation may look like this
class ObjectVisitor : IVisitor {
private StringBuilder sb = new StringBuilder();
public void VisitAnd(Object obj) {
sb.Append("(");
var and = "";
foreach (var child in obj.Children) {
sb.Append(and);
child.AsVisitable().Accept(this);
and = "and";
}
sb.Append(")");
}
public void VisitEquals(Object obj) {
// Assuming equal object must have exactly one child
// Which again is a sign that visitor pattern is not bla bla...
sb.Append("(")
.Append(obj.Children[0].Name);
.Append(" Equals ");
.Append(obj.Children[0].Value);
.Append(")");
}
}
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
First of all you have wrong order in the result.Second, somethimes you miss brackets in the result.Final it should be:
(((Name IsEqual 5) And (Name IsEqual 6)) And (Name IsEqual 3))
To complete this task you should use recursive function.
static IEnumerable<string> ReturnString(Obj val)
{
foreach (Obj node in val.Childs)
yield return ConvertToString(node);
}
static string ConvertToString(Obj val)
{
switch(val.Name)
{
case "Operator":
{
return string.Format("({0} {1} {2})", val.Childs[0].Name, val.Value, val.Childs[0].Value);
}
case "Method":
{
IEnumerable<string> coll = ReturnString(val);
StringBuilder final = new StringBuilder();
final.Append("(");
IEnumerator<string> e = coll.GetEnumerator();
e.MoveNext();
final.Append(string.Format("{0}", e.Current, val.Value));
while (e.MoveNext())
{
final.Append(string.Format(" {0} {1}", val.Value, e.Current));
}
final.Append(")");
return final.ToString();
}
case "Name":
return Convert.ToString(val.Value);
}
return "-";
}
Below is your example in the code:
string s = ConvertToString(new Obj
{
Name = "Method",
Value = "And",
Childs = new List<Obj>
{
new Obj()
{
Name = "Method",
Value = "And",
Childs = new List<Obj>
{
new Obj()
{
Name = "Operator",
Value = "IsEqual",
Childs = new List<Obj>
{
new Obj()
{
Name="Name",
Value="5",
Childs=null
}
}
},
new Obj()
{
Name = "Operator",
Value = "IsEqual",
Childs = new List<Obj>
{
new Obj()
{
Name="Name",
Value="6",
Childs=null
}
}
}
}
},
new Obj()
{
Name = "Operator",
Value = "IsEqual",
Childs = new List<Obj>
{
new Obj()
{
Name="Name",
Value="3",
Childs=null
}
}
}
}
});
This might not be what you want. But one way to create the output that you want without using the Visitor pattern is add the following method to the Object class, like this:
public string Format()
{
if (Name == "Operator")
{
if(Childs == null || Childs.Count != 1)
throw new Exception("Invalid Childs");
Object chlid = Childs[0];
return chlid.Name + " IsEqual " + chlid.Value;
}
if (Name == "Method")
{
if(Childs == null || Childs.Count == 0)
throw new Exception("Invalid Childs");
var str = " " + Value + " ";
return string.Join(str, Childs.Select(x => "(" + x.Format() + ")"));
}
throw new Exception("Format should only be invoked on Operator/Method");
}

Categories