C# .NET: Deserialize json tree - c#

Good evening,
For my specific problem, I have to create a menu (a tree of menus more exactly). Thus, I decided to use Composite Design Pattern with the following structure:
IMenuComponent (an interface defining some properties and so on)
Menu (contains a list of IMenuComponent)
MenuEntry (a leaf)
So, I'll have to navigate through it while knowing the way back up. The obvious answer is having a 'parent' property.
I have the following json tree:
{
"Guid": "08967257-9306-4717-a76a-e1a4f0050505",
"Parent": null,
"Title": "Main Menu",
"Message": "A sample message",
"Elements": [
{
"$type": "Menu",
"Guid": "26dfca59-9163-4b11-8033-e8ad13f3f5cc",
"Parent": "08967257-9306-4717-a76a-e1a4f0050505",
"Title": "Option 1",
"Message": "Another sample message",
"Elements": [
{
"$type": "MenuEntry",
"Parent": "26dfca59-9163-4b11-8033-e8ad13f3f5cc",
"Title": "Entry 1",
"Message": "Another sample message"
},
{
"$type": "MenuEntry",
"Parent": "26dfca59-9163-4b11-8033-e8ad13f3f5cc",
"Title": "Entry 2",
"Message": "Another sample message"
},
{
"$type": "MenuEntry",
"Parent": "26dfca59-9163-4b11-8033-e8ad13f3f5cc",
"Title": "Entry 3",
"Message": "Another sample message"
}
]
},
{
"$type": "MenuEntry",
"Parent": "08967257-9306-4717-a76a-e1a4f0050505",
"Title": "Option 2",
"Message": "Another sample message"
}
]
}
This isn't about the deserialization itself, since that's working as it should.
My problem is that I'll have to access the parents after deserializing the file to a 'Menu' (the root).
I can think of two ways:
Find a 'parent' in runtime using its Guid - can be bad performance-wise depending on tree size;
Add a 'Menu' property to both 'Menu' and 'MenuEntry' classes to store the 'parent' and fill it, by finding the corresponding Guid, after deserializing from json. Basically, I'd put it together as soon as the application starts, preventing the find in runtime. It could be bad performance-wise as well.
What's the way to go? And how should I do it?
As a side note, I'm using Newtonsoft.Json
Thanks for the help!

#Ian Mercer comment actually helped me figure out a simple solution (using the 2nd way described in my post above).
The 2nd way doesn't really rely on Guids. After the deserialize, I can just iterate over each menu collection (as I probably would anyway) and give the reference to the 'parent' directly.
private void ConnectTree(Menu menu)
{
foreach (IMenuComponent component in menu.Elements) {
if (component is Menu) {
(component as Menu).ParentMenu = menu;
ConnectTree (component as Menu);
}
else if (component is MenuEntry) {
(component as MenuEntry).ParentMenu = menu;
}
}
}
Just need to call ConnectTree(_deserializedMenu)
Edit:
#Brian Rogers just mentioned PreserveReferencesHandling property from json net. The original problem was all about json not supporting circular references, but that solves it - it's the same logic as using Guids. There are multiple solutions, which is always good to know.
Thanks!

Related

Deserializing JSON using UnityEngine.JsonUtility returns null list

I am trying to mod a Unity game using BepInEx. I want to read a list of a type of class I've created from a JSON, then read each object from that class. I used UnityEngine.JsonUtility to do this and created a wrapper class containing my list.
[System.Serializable]
public class StarSignDataList
{
public List<StarSignData> starSigns;
}
// Stores all data pertaining to star signs as read from JSON file.
[System.Serializable]
public class StarSignData
{
public string name;
public int id;
public string description;
}
Then I read the json file from the correct file path, parsing that information into a string and called the JsonUtility.FromJason() method to convert the string into my StarSignDataList class.
// Reads all star sign data from JSON and assigns values to list.
public static void InitializeData()
{
log.LogInfo("Initializing Star Sign data.");
string json = File.ReadAllText("BepInEx/plugins/StarSigns/starsigns.json");
log.LogInfo(json);
StarSignDataList list = UnityEngine.JsonUtility.FromJson<StarSignDataList>(json);
log.LogWarning(list.starSigns.Count == 0 ? "Not null" : "Null!");
log.LogInfo(list.starSigns[0].name);
signs = list.starSigns;
foreach (StarSignData data in signs)
{
log.LogInfo(data.name);
}
}
When I print out the contents of my json immediately after reading it, it comes out as expected, looking exactly like my json file. However when I convert the json string into my list class, the list is completely empty and then throws a NullReferenceException when trying to print the first object. I've trying using arrays instead of lists, I've checked that my json is correctly written, I've pored over countless google search results without success.
Here is my json file.
{
"starSigns": [
{
"name": "Sign of the Fool",
"id": 0,
"description": "You are of normal birth."
},
{
"name": "Sign of Loki",
"id": 1,
"description": "The sign of the god of betrayal shone above your birth place."
},
{
"name": "Sign of Odin",
"id": 2,
"description": "A tumultuous child at birth, your muscles bear the strength of Odin himself."
}
]
}
Out of desperation I tried converting a class like this to a json, and it just wrote to the file "{ }". When I try to do the same with just my StarSignData class it works as expected, but I need to put multiple objects in my json and the list class does not seem to work. I also can't use external libraries such as SimpleJson for this.

Parsing InfluxDB result using JSON.net

I'm trying to make a command line utility for initializing a InfluxDB database, but I'm pretty new to influx, and C# in general.
With the following response from the Influx DB Database, I'm trying to pretty print this in the console window.
Ideally I would have errors show up in the standard error buffer, and warnings or info's show up in the standard output.
However, when running the code below in a debug environment, messages appears to be in an incorrect format according to several jsonpath checkers that I have used.
JSON input as result.Body
{
"results": [
{
"statement_id": 0,
"messages": [
{
"level": "warning",
"text": "deprecated use of 'CREATE RETENTION POLICY Primary ON SensorData DURATION 30d REPLICATION 1' in a read only context, please use a POST request instead"
}
]
}
]
}
JSON output as messages prior to transformation:
messages {{
"level": "warning",
"text": "deprecated use of 'CREATE RETENTION POLICY Primary ON SensorData DURATION 30d REPLICATION 1' in a read only context, please use a POST request instead"
}} Newtonsoft.Json.Linq.JToken {Newtonsoft.Json.Linq.JObject}
As you can see, the messages output is in a nested object {{}} rather then an array as expected...
According to https://jsonpath.curiousconcept.com/ and several other jsonpath checkers, I was expecting something similar to:
[
{
"level":"warning",
"text":"deprecated use of 'CREATE RETENTION POLICY Primary ON SensorData DURATION 30d REPLICATION 1' in a read only context, please use a POST request instead"
}
]
C#
private static void PrintResult(IInfluxDataApiResponse result)
{
var output = result.Success ? System.Console.Out : System.Console.Error;
output.WriteLine("["+result.StatusCode + "] : "+result.Body);
var json = JObject.Parse(result.Body);
var messages = json.SelectToken("$.results[*].messages[*]"); //outputs an array of messages if exists. e.g. [{level:warning,text:test}]
if (messages != null)
{
var transformed = messages.Select(m => new { level = (string)m["level"], text = (string)m["text]"] }).ToList();
foreach (var msg in transformed)
{
output.WriteLine($"[{result.StatusCode}] : {msg.level} - {msg.text}");
}
}
}
For my uses at least, using var messages =
json.SelectTokens("$.results[*].messages[*]");
rather then
json.SelectToken("$.results[*].messages[*]");
allowed me to workaround the issue, as I could then treat the result as a C# enumerable, as opposed to special casing 1 result vs many results for SelectToken as it seems to flatten single results into an object, where as other implementations would have it be an array.

Transfer values between 2 config files

I have a problem, and i can't figure this out myself..
In my program i have an auto updater, when my program updates a new(changed, some new keys) config file is created. what i want my program to do is, is when it's updating to look at both config files(old and new) and transfer old settings that match a key in the new file to the new file.
This is an example of the old file:
{
"Setting1": false,
"Setting2": 123,
"Setting3": "test",
"LocationList": {
"Country": "NL",
"Locations": [
{
"Latitude": 38.556807486461118,
"Longitude": -121.2383794784546
},
{
"Latitude": -33.859019,
"Longitude": 151.213098
},
{
"Latitude": 47.5014969,
"Longitude": -122.0959568
},
{
"Latitude": 51.5025343,
"Longitude": -0.2055027
}
]
}
}
And this can be the new file(can also be different):
{
"Setting1": null,
"Setting2": null,
"Setting3": "",
"Setting4": ""
"LocationList": {
"Country": "",
"Locations": [
{
"Latitude": null,
"Longitude": null
},
{
"Latitude": null,
"Longitude": null
}
]
}
}
Expected result:
{
"Setting1": false,
"Setting2": 123,
"Setting3": "test",
"Setting4": ""
"LocationList": {
"Country": "NL",
"Locations": [
{
"Latitude": 38.556807486461118,
"Longitude": -121.2383794784546
},
{
"Latitude": -33.859019,
"Longitude": 151.213098
},
{
"Latitude": 47.5014969,
"Longitude": -122.0959568
},
{
"Latitude": 51.5025343,
"Longitude": -0.2055027
}
]
}
}
First, i looked at creating a class in c# and just deserialize it, then, i came to the conclusion that this is not possible because i don't know what the config is going to look like.
Second, i thought using a dynamic would do the trick, it didn't, because i didn't knew any keys that were in it. And couldn't figure out how to figure that out.
And lastly, i've looked if it would be possible using regex, for me, this seems impossible..
Can anybody give me some ideas of how they would do it? I don't need code, just a push in the right direction.
P.S. i do not want to combine the two, when there is a key in the old file but not in the new one, it doesn't need to be transferred(Only lists will be completely transferred from the old file, also when the list is empty/filled in the new one).
If you really want to try something in JSON, I can only recommend the excellent JSON.Net library to parse the json for you. Using LINQ to JSON you could easily find matching keys in a recursive fashion between the old config file and the newer one and simply copy the value from one to the other
see documentation and a small example at http://www.newtonsoft.com/json/help/html/LINQtoJSON.htm
briefly you could do so in pseudoCode. Obviously this would not be super performant because you would recursively walk two files a lot of times and could be optimized, but at the same time unless your configuration files are monstrous blobs this should not pose any problem on any kind of modern hardware
//load both config files
//load the first token in original file and walk recursively
//for each token try to match in the new file and write data if required using the same recursive technique to walk the other file
I don't need code, just a push in the right direction.
Store your configuration in a regular App.config file and leverage the System.Configuration. Depending on the configuration complexity you can either use the ready <appSettings> or construct your own config section(s) and elements. It still be easier and error proof than doing custom config.
The matter is too complicated to start inventing the wheel just to use another (text) format. Even if you solve your current problem, there will be more, which are already solved somewhere within the System.Configration...
You can start exploring the configuration options here; anyways a regular search in your favorite search engine will do the trick...
Oke after some trouble i've managed to fix it(Thanks #Louis).
This is how i've done it:
private static bool TransferConfig(string baseDir, ISession session)
{
//if (!session.LogicSettings.TransferConfigAndAuthOnUpdate)
// return false;
var configDir = Path.Combine(baseDir, "Config");
if (!Directory.Exists(configDir))
return false;
var oldConf = GetJObject(Path.Combine(configDir, "config.json.old"));
var oldAuth = GetJObject(Path.Combine(configDir, "auth.json.old"));
GlobalSettings.Load("");
var newConf = GetJObject(Path.Combine(configDir, "config.json"));
var newAuth = GetJObject(Path.Combine(configDir, "auth.json"));
TransferJSON(oldConf, newConf);
TransferJSON(oldAuth, newAuth);
File.WriteAllText(Path.Combine(configDir, "config.json"), newConf.ToString());
File.WriteAllText(Path.Combine(configDir, "auth.json"), newAuth.ToString());
return true;
}
private static JObject GetJObject(string filePath)
{
return JObject.Parse(File.ReadAllText(filePath));
}
private static bool TransferJSON(JObject oldFile, JObject newFile)
{
try
{
foreach (var newProperty in newFile.Properties())
foreach (var oldProperty in oldFile.Properties())
if (newProperty.Name.Equals(oldProperty.Name))
{
newFile[newProperty.Name] = oldProperty.Value;
break;
}
return true;
}
catch
{
return false;
}
}

Load multiple 'nodes' from JSON and store into array

I am currently creating a small Text-Based Game. In this there are obviously multiple rooms, I wish to load those rooms from a JSON file. I am currently doing that as such:
dynamic jRooms = Json.Decode(file);
for (int i = 0; i < Regex.Matches( file, "Room" ).Count; i++){
name[i] = jRooms.Game.Room[i];
description[i] = jRooms.Game.Room.Attributes.Description[i];
exits[i] = jRooms.Game.Room.Attributes.Exits[i];
_count++;
}
That loads information from the following JSON file:
{
'Game': [{
'Room': 'Vault 111 Freeze Chamber',
'Attributes': {
'Description': 'The freeze chamber of the vault you entered after the nuclear fallout.',
'Exits': 'North.Vault 111: Main Hallway'
},
'Room': 'Vault 111 Main Hallway',
'Attributes': {
'Description': 'The main hallway of the vault.',
'Exits': 'South.Vault 111: Freeze Chamber'
}
}]}
This unfortunately throws up an error during run time that I can't seem to work out, which is the following:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot perform runtime binding on a null reference
at CallSite.Target(Closure , CallSite , Object , Int32 )
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at TBA.Loader.Rooms()
at TBA.Program.Main(String[] args)
Any help would be greatly appreciated, because I am completely stumped as to what is wrong and not working. If you need anymore of my code, just request it.
Thanks.
The problem is with your JSON. JSON doesn't allow single quotes (maybe they have a different meaning or no meaning at all). Source - W3Schools.
Use services like JSONLint to validate JSON and check for errors. Even JSONLint declares your JSON, invalid. Using double quotes however, it is declared valid. You should use double quotes like this:
{
"Game": [
{
"Room": "Vault111FreezeChamber",
"Attributes": {
"Description": "Thefreezechamberofthevaultyouenteredafterthenuclearfallout.",
"Exits": "North.Vault111: MainHallway"
},
"Room": "Vault111MainHallway",
"Attributes": {
"Description": "Themainhallwayofthevault.",
"Exits": "South.Vault111: FreezeChamber"
}
}
]
}

What are the groups of four dashes in the .NET reference source code?

I was browsing the source of the PluralizationService when I noticed something odd. In the class there are a couple of private dictionaries reflecting different pluralisation rules. For example:
private string[] _uninflectiveWordList =
new string[] {
"bison", "flounder", "pliers", "bream", "gallows", "proceedings",
"breeches", "graffiti", "rabies", "britches", "headquarters", "salmon",
"carp", "----", "scissors", "ch----is", "high-jinks", "sea-bass",
"clippers", "homework", "series", "cod", "innings", "shears", "contretemps",
"jackanapes", "species", "corps", "mackerel", "swine", "debris", "measles",
"trout", "diabetes", "mews", "tuna", "djinn", "mumps", "whiting", "eland",
"news", "wildebeest", "elk", "pincers", "police", "hair", "ice", "chaos",
"milk", "cotton", "pneumonoultramicroscopicsilicovolcanoconiosis",
"information", "aircraft", "scabies", "traffic", "corn", "millet", "rice",
"hay", "----", "tobacco", "cabbage", "okra", "broccoli", "asparagus",
"lettuce", "beef", "pork", "venison", "mutton", "cattle", "offspring",
"molasses", "shambles", "shingles"};
What are the groups of four dashes in the strings? I did not them see handled in the code, so they're not some kind of a template. The only thing I can think of is that those are censored expletives ('ch----is' would be 'chassis'), which in this case is actually hurting the readability. Did anyone else come across this? If I were to be interested in the actual full list, how would I view it?
From using Reflector to look at the decompiled code I can verify that the compiled version doesn't have "----" in there and it does indeed seem to be some kind of censorship somewhere along the way. The decompiled code has this in the constructor:
this._uninflectiveWordList = new string[] {
"bison", "flounder", "pliers", "bream", "gallows", "proceedings", "breeches", "graffiti", "rabies", "britches", "headquarters", "salmon", "carp", "herpes", "scissors", "chassis",
"high-jinks", "sea-bass", "clippers", "homework", "series", "cod", "innings", "shears", "contretemps", "jackanapes", "species", "corps", "mackerel", "swine", "debris", "measles",
"trout", "diabetes", "mews", "tuna", "djinn", "mumps", "whiting", "eland", "news", "wildebeest", "elk", "pincers", "police", "hair", "ice", "chaos",
"milk", "cotton", "pneumonoultramicroscopicsilicovolcanoconiosis", "information", "aircraft", "scabies", "traffic", "corn", "millet", "rice", "hay", "hemp", "tobacco", "cabbage", "okra", "broccoli",
"asparagus", "lettuce", "beef", "pork", "venison", "mutton", "cattle", "offspring", "molasses", "shambles", "shingles"
};
As you can see the censored words are "herpes", "chassis" and "hemp" (if I've followed along correctly). None of which I personally think need censoring which suggests it is some kind of automated system doing it. I would assume that the original source has them in rather than them being added in some kind of precompile merge (if nothing else because "----" really isn't enough for anything to say what it should be replaced with). I'd imagine for some reason the reference website gets them censored.
Hans Passant also in comments linked to an answer to a very similar question: What does ----s mean in the context of StringBuilder.ToString()? . This explains that "The source code for the published Reference Source is pushed through a filter that removes objectionable content from the source".

Categories

Resources