List to Dictionary and back again - c#

I have read many posts about dictionaries and serialization etc. and they all have different ways of doing things.
Being new to coding (to an extent) I am having trouble figuring out how to get my dictionary to a list or serialized to get it saved then reloaded on play. This is Unity BTW.
So here is what I am using.
I have an application manager that is mainly just a set of BOOL values that tells my mission system if something has been completed or not. Something like "GetWeapon FALSE" (then when they pick it up it changes to TRUE which tells the mission system to move to the next objective or complete)
So I have a starting list of keys,values...these then get placed into a dictionary and the values are changed within that.
I need to be able to save those values and reload them on LOAD (default PLAY mode is newgame--as you see below it resets the dictionary and copies in the starting states). I know it can't be as difficult as I am making it, just not understanding the whole serialize thing.
Most sites are showing a dictionary like {key,value} so I am getting lost on iterating through the dictionary and capturing the pairs and saving them.
Here is partial code for the appmanager (it is a singleton):
// This holds any states you wish set at game startup
[SerializeField] private List<GameState> _startingGameStates = new List<GameState>();
// Used to store the key/values pairs in the above list in a more efficient dictionary
private Dictionary<string, string> _gameStateDictionary = new Dictionary<string, string>();
void Awake()
{
// This object must live for the entire application
DontDestroyOnLoad(gameObject);
ResetGameStates();
}
void ResetGameStates()
{
_gameStateDictionary.Clear();
// Copy starting game states into game state dictionary
for (int i = 0; i < _startingGameStates.Count; i++)
{
GameState gs = _startingGameStates[i];
_gameStateDictionary[gs.Key] = gs.Value;
}
OnStateChanged.Invoke();
}
public GameState GetStartingGameState(string key)
{
for (int i = 0; i < _startingGameStates.Count; i++)
{
if (_startingGameStates[i] != null && _startingGameStates[i].Key.Equals(key))
return _startingGameStates[i];
}
return null;
}
// Name : SetGameState
// Desc : Sets a Game State
public bool SetGameState(string key, string value)
{
if (key == null || value == null) return false;
_gameStateDictionary[key] = value;
OnStateChanged.Invoke();
return true;
}
Tried something similar to this:
Dictionary<string, string> _gameStateDictionary = new Dictionary<string, string>
{
for (int i = 0; i < _gameStateDictionary.Count; i++)
string json = JsonConvert.SerializeObject(_gameStateDictionary, Formatting.Indented);
Debug.Log(json);
{
}
However all I got was the first item in the list. (I did modify the above in a for loop) I know the above is wrong, I did other iterations to just to get the dictionary to print out in the console.
Anyway, just asking for a little code help to save and load a dictionary.

Since you want to save and load those persistent anyway, personally I would not use the Inspector and bother with Unity serialization at all but rather directly serialize/deserialize from and to json.
Simply place your default dictionary as a simple json file like e.g.
{
"Key1" : "Value1",
"Key2" : "Value2",
"Key3" : "Value3",
...
}
within the folder Assets/StreamingAssets (create it if it doesn't exists). This will be your default fallback file if none exists so far.
On runtime you will load it from Application.streamingAssetsPath.
Then when saving you will not put it there but rather to the Application.persistentDataPath
using Newtonsoft.Json;
...
[SerializeField] private string fileName = "example.json";
private string defaultStatesPath => Path.Combine(Application.streamingAssetsPath, fileName);
private string persistentStatesPath => Path.Combine(Application.persistentDataPath, fileName);
private Dictionary<string, string> _gameStateDictionary
public void Load()
{
var filePath = persistentStatesPath;
if(!File.Exists(filePath))
{
filePath = defaultStatesPath;
}
var json = File.ReadAllText(filePath);
_gameStateDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
}
public void Save()
{
var filePath = persistentStatesPath;
if(!Directory.Exists(Application.persistentDataPath))
{
Directory.CreateDirectory(Application.persistentDataPath);
}
var json = JsonConvert.SerializeObject(_gameStateDictionary, Formatting.intended);
File.WriteAllText(filePath, json);
}
If you then still want to edit the default via the Inspector you could merge both and instead of using StreamingAssets do
[SerializeField] private GameState[] defaultStates;
public void Load()
{
var filePath = persistentStatesPath;
if(!File.Exists(filePath))
{
LoadDefaults();
return;
}
var json = File.ReadAllText(filePath);
_gameStateDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
}
public void LoadDefaults()
{
_gameStateDictionary = defaultStates.ToDictionary(gameState => gameState.Key, gameState => gameState.Value);
}
And then btw you could do
public string GetState(string key, string fallbackValue = "")
{
if(_gameStateDictionary.TryGetValue(key, out var value))
{
return value;
}
return fallbackValue;
}

You can iterate the dictionary by using its Keys collection.
foreach (var key in myDictionary.Keys)
{
var val = myDictionary[key];
}
Populate your data structure with the key/value pairs and serialize the list of items.
var missionItems = new List<MissionItem>();
foreach (var key in myDictionary.Keys)
{
var missionItem = new MissionItem();
missionItem.Key = key;
missionItem.Value = myDictionary[key];
missionItems.Add(missionItem);
}
var json = JsonUtility.ToJson(missionItems);
Loading is straight forward, deserialize your data, then loop through all the items and add each one to the empty dicitionary.
var missionItems = JsonUtility.FromJson<List<MissionItem>>(json);
foreach (var item in missionItems)
{
myDictionary.Add(item.Key, item.Value);
}

Here is my final setup.
I call these on button press in another script (the UI pause menu save/load buttons)
private bool useEncryption = true;
private string encryptionCodeWord = "CodeWordHere";
private static string fileName = "name.json";
private static string defaultStatesPath => Path.Combine(Application.streamingAssetsPath, fileName);
private static string persistentStatesPath => Path.Combine(Application.persistentDataPath, fileName);
public void SaveStates(int slotIndex)
{
var filePath = persistentStatesPath;
if(!Directory.Exists(Application.persistentDataPath))
{
Directory.CreateDirectory(Application.persistentDataPath);
}
var json = JsonConvert.SerializeObject(_gameStateDictionary, Formatting.Indented);
if (useEncryption)
{
Debug.Log("Using Encryption");
json = EncryptDecrypt(json);
}
File.WriteAllText(filePath + slotIndex, json);
}
public void LoadStates(int SlotIndex)
{
var filePath = persistentStatesPath;
if(!File.Exists(filePath + SlotIndex))
{
filePath = defaultStatesPath;
}
var json = File.ReadAllText(filePath + SlotIndex);
if (useEncryption)
{
Debug.Log("Using Encryption");
json = EncryptDecrypt(json);
}
_gameStateDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
}
private string EncryptDecrypt(string data)
{
string modifiedData = "";
for (int i = 0; i < data.Length; i++)
{
modifiedData += (char)(data[i] ^ encryptionCodeWord[i % encryptionCodeWord.Length]);
}
return modifiedData;
}

Related

Is there a built-in method to convert .NET Configuration to JSON?

It's easy to convert JSON into configuration, e.g. with
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
var configuration = new ConfigurationBuilder().AddJsonStream(stream).Build();
which gives you an IConfigurationRoot.
Is there a method (preferably in one of the Microsoft.Extensions.Configuration packages) that does the reverse?
Context: I'm downloading a bunch of Azure App Configuration settings and want to export them as JSON. Similar functionality is available in the Azure Portal but I want to resolve key vault references as well.
I can probably do something like this:
// Convert to JSON
var jRoot = new JObject();
foreach (var setting in settings) {
Add(jRoot, setting.Key, setting.Value);
}
with the Add method defined as
private void Add(JObject jObject, string key, string value) {
var index = key.IndexOf(':');
if (index == -1) {
jObject[key] = value;
return;
}
var prefix = key[..index];
if (!jObject.ContainsKey(prefix)) {
jObject[prefix] = new JObject();
}
Add((JObject)jObject[prefix], key[(index + 1)..], value);
}
which I'd probably need to extend to support arrays, but I was hoping I'd not have to reinvent the wheel.
For now, I've expanded the Add method to support arrays:
private void Add(JToken jToken, string key, string value) {
var components = key.Split(":", 3);
if (components.Length == 1) {
// Leaf node
if (jToken is JArray jArray_) {
jArray_.Add(value);
} else {
jToken[components[0]] = value;
}
return;
}
// Next level
JToken nextToken;
var nextTokenIsAnArray = int.TryParse(components[1], out _);
if (jToken is JArray jArray) {
var index = int.Parse(components[0]);
if (jArray.Count == index) {
nextToken = nextTokenIsAnArray ? new JArray() : (JToken)new JObject();
jArray.Add(nextToken);
} else {
nextToken = jArray[index];
}
} else {
nextToken = jToken[components[0]];
if (nextToken == null) {
nextToken = jToken[components[0]] = nextTokenIsAnArray ? new JArray() : (JToken)new JObject();
}
}
Add(nextToken, key[(components[0].Length + 1)..], value);
}
which works for me, assuming arrays appear in the right order, e.g.
key "Foo:0", value "Bar"
key "Foo:1", value "Baz"
which gets serialized as
{ "Foo": ["Bar", "Baz"] }

Listbox DisplayValue for Newtonsoft JToken

I have a List (from Newtonsoft), and I'm trying to define a DisplayValue for Microsoft .NET list box.
List<JToken> gCollectionRequests = new List<JToken>();
//Code here to create a valid gCollectionRequests
listBox1.DataSource = gCollectionRequests;
listBox1.DisplayMember = gCollectionRequests[0]["name"].Value<string>();
The List is based on a JSON Postman collection file, whose first field is "name". That's what I want to display. The line line above doesn't break the code, it just doesn't have any effect.
An example of the gCollectionRequests:
What is the right way to define the DisplayValue from List source?
Thanks, #SriramSakthivel. That was the key. So I made a new object that incorporated my JSON collection. Is this how you would have done it?
public class CombinedJTokens
{
private int RequestCount; //Not really needed.
public JToken MyRequest;
public CombinedJTokens(int myCount, JToken myToken)
{
this.RequestCount = myCount;
this.MyRequest = myToken;
}
public string DisplayName
{
get { return string.Format("This is a JToken: {0}", MyRequest["name"].Value<string>()); }
}
}
Then, this handles filling in the listbox:
private void FillInListBoxWithRequests()
{
List<CombinedJTokens> myCombinedTokensList = new List<CombinedJTokens>();
int theCount = 0;
foreach (var item in gCollectionRequests)
{
JToken myNewJToken = item;
CombinedJTokens myCombinedJTokens = new CombinedJTokens(++theCount, item);
myCombinedTokensList.Add(myCombinedJTokens);
}
listBox1.DataSource = null;
listBox1.DataSource = myCombinedTokensList;
listBox1.DisplayMember = "DisplayName";
}
gCollectionRequests is a list of the JSON requests defined as:
List<JToken> gCollectionRequests = new List<JToken>();

How to update 2 separate nodes in Firebase Realtime Database

I have problems with transactions using Firebase. I want to update 2 nodes (models and scenes) at the same time but it never updates my database.
Here is my code:
public static void Delete(DataContainers.Scene iScene)
{
DatabaseReference leaderBoardRef = FirebaseDatabase.DefaultInstance.RootReference;
leaderBoardRef.RunTransaction(mutableData =>
{
mutableData.Child("Scenes/" + iScene.id).Value = null;
foreach (var _model in iScene.models)
{
mutableData.Child("models/" + _model).Value = null;
}
return TransactionResult.Success(mutableData);
});
}
Is there any other way how to do it?
You can use a multi-path update to delete all the data at once like you desire:
private void deleteScene(DataContainers.Scene iScene) {
// Get root database reference
DatabaseReference mDatabase = FirebaseDatabase.DefaultInstance.RootReference;
// Initialize a new list of "path->value" pairs
Dictionary<string, Object> childUpdates = new Dictionary<string, Object>();
// Delete the given scene and all of its models
childUpdates["/Scenes/" + iScene.id] = null;
foreach (var _model in iScene.models)
{
childUpdates["/models/" + _model] = null;
}
mDatabase.UpdateChildrenAsync(childUpdates);
}

Filepath not found outside WPF window ctor

I am using following world heatmap in my WPF application:
https://lvcharts.net/App/examples/v1/wpf/GeoHeatMap
//Geomap
private static string GEOMAP_PATH = System.IO.Path.GetDirectoryName(System.AppDomain.CurrentDomain.BaseDirectory) + "\\Materials\\GeoMaps\\World.xml";
public MainMaterialWindow()
{
//Default application initialization
InitializeComponent();
//geomap chart
var values = new Dictionary<string, double>();
foreach (actual_headmapdata data in GetWorldHeadmapData())
{
values[data.isocode] = data.count;
}
geoMap1.HeatMap = values;
var lang = new Dictionary<string, string>();
lang["DE"] = "Germany"; // change the language if necessary
geoMap1.LanguagePack = lang;
geoMap1.Source = GEOMAP_PATH;
}
What I don't understand is why the filepath is not found if I do something like this.
//Geomap
private static string GEOMAP_PATH = System.IO.Path.GetDirectoryName(System.AppDomain.CurrentDomain.BaseDirectory) + "\\Materials\\GeoMaps\\World.xml";
public MainMaterialWindow()
{
//Default application initialization
InitializeComponent();
//init GeoMap
initGeoMap();
}
public void initGeoMap() {
//geomap chart
var values = new Dictionary<string, double>();
foreach (actual_headmapdata data in GetWorldHeadmapData())
{
values[data.isocode] = data.count;
}
geoMap1.HeatMap = values;
var lang = new Dictionary<string, string>();
lang["DE"] = "Germany"; // change the language if necessary
geoMap1.LanguagePack = lang;
geoMap1.Source = GEOMAP_PATH;
}
I get: System.IO.FileNotFoundException: "This file was not found."
So the problem is, why the filepath is not valid anymore once the initialization of the geomap is outside of MainMaterialWindow(). File still exits.
XAML:
<lvc:GeoMap x:Name="geoMap1" HeatMap="{Binding Values}" LanguagePack="{Binding LanguagePack}" Margin="10,0,-12,22" />
Same happens with full filepath as string: GEOMAP_PATH="D:\\Dev\\projectname\\bin\\debug\\Materials\\GeoMaps\\World.xml"
This also only works within MainMaterialWindow(), so it is not about:
private static string GEOMAP_PATH = System.IO.Path.GetDirectoryName(System.AppDomain.CurrentDomain.BaseDirectory)

How to parse JSON data to generate a C# sample code which demonstrates Request Body Object creation

I need to write many sample codes for various API's, which demonstrate how to write the code to use those particular REST API's in C# language.
Now for the API's which are HTTP POST calls, a request body is first created as a class object which is then later serialized into a JSON string and passed to the REST Client.
My Requirement is in this part: "Creating the request Body as a class object".
Following example will make the requirement crystal clear:-
Suppose i have the below JSON Data:
{
"workingDays": ["Monday","Wednesday","Friday"],
"employeeInformation": {
"employeeID": "12345",
"name": {
"firstName": "foo",
"lastName": "bar"
}
},
"joiningDate":"23061984"
}
I need to parse the above data and generate the below code (which currently i am writing manually):
// Create Main Request Body Object
var requestBodyObj = new RequestBody();
// Array Case
var workingDaysObj = new List<string>();
workingDaysObj.Add("Monday");
workingDaysObj.Add("Wednesday");
workingDaysObj.Add("Friday");
requestBodyObj.workingDays = workingDaysObj;
// Nested Object Case
var employeeInformationObj = new employeeInformation();
employeeInformationObj.employeeID = "12345";
var nameObj = new name();
nameObj.firstName = "foo";
nameObj.lastName = "bar";
employeeInformationObj.name = nameObj;
requestBodyObj.employeeInformation = employeeInformationObj;
// Simple Name/Value Pair
requestBodyObj.joiningDate = "23061984";
So as per the above example the JSON Data can be in one of the following 2 forms as well (apart from simple name/value pairs):
Array
Nested Object
And both these cases should be handled as shown in the above code.
Note: User will not be provided with a JSON file so i can't write any code which directly reads a JSON file, deserializes it and assigns the values to a class object using (for example) a NewtonSoft function:
// read file into a string and deserialize JSON to a type
Movie movie1 = JsonConvert.DeserializeObject<Movie>(File.ReadAllText(#"c:\movie.json"));
// deserialize JSON directly from a file
using (StreamReader file = File.OpenText(#"c:\movie.json"))
{
JsonSerializer serializer = new JsonSerializer();
Movie movie2 = (Movie)serializer.Deserialize(file, typeof(Movie));
}
I just want a simple "JSON parser and C# Code generator" (Preferably written in C# language itself).
Any suggestions or pointers will be appreciated.
Edit Update
Pascal Case setting for variable names
Json Object names can be mapped to Project model classes
Write the output to text file for easy copy
Updated Code Below:-
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
namespace SampleCodeCreator
{
class Program
{
// Declaring the variables
static string jsonFilePath = #"[Your File Path]";
static string outputFilePath = #"[Your File Path]";
static string jsonData;
static Dictionary<string, string> classMap = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
static void Main(string[] args)
{
// Initializing Class map which is used to map the json object to Project Model Class
InitializeClassMap();
// clear current data in the output file
using (System.IO.StreamWriter file = new System.IO.StreamWriter(outputFilePath, false))
{
file.Write(String.Empty);
}
// read the json data file and store the data in a simple string
using (StreamReader r = new StreamReader(jsonFilePath))
{
jsonData = r.ReadToEnd();
}
// Call the method for the whole json data
PrintJsonObject("RequestBody", jsonData);
}
static void PrintJsonObject(string parentObject, string jsonData)
{
// if the parent object has any mapped class, then set the object name
parentObject = MappedClass(parentObject);
Console.WriteLine("var {0}Obj = new {1}();", ToCamelCase(parentObject), parentObject);
SetOutput("var " + ToCamelCase(parentObject) + "Obj = new " + parentObject + "();");
Console.WriteLine("");
SetOutput("");
// Deserialize the Json data and iterate through each of its sub-sections
var jsonSubData = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonData);
foreach (var data in jsonSubData)
{
var dataKey = data.Key;
var dataValue = data.Value;
// array case (if the sub element is an array)
if (dataValue.ToString().Contains("["))
{
PrintArrayCase(dataKey, dataValue);
Console.WriteLine("{0}Obj.{1} = {1}Obj;", ToCamelCase(parentObject), dataKey);
SetOutput(ToCamelCase(parentObject) + "Obj." + dataKey + " = " + dataKey + "Obj;");
Console.WriteLine("");
SetOutput("");
}
// nested object case (if the sub element itself contains another json format body)
else if (dataValue.ToString().Contains("{"))
{
// Recursive Call
PrintJsonObject(dataKey, dataValue.ToString());
Console.WriteLine("{0}Obj.{1} = {1}Obj;", ToCamelCase(parentObject), dataKey);
SetOutput(ToCamelCase(parentObject) + "Obj." + dataKey + " = " + dataKey + "Obj;");
Console.WriteLine("");
SetOutput("");
}
// simple key value pair case
else
{
PrintKeyValuePairCase(parentObject, dataKey, dataValue.ToString());
}
}
}
static void PrintArrayCase(string key, object obj)
{
Console.WriteLine("var {0}Obj = new List<string>();", key);
SetOutput("var " + key + "Obj = new List<string>();");
// The array value is split into its values
// e.g. [abc, def, ghi] -> [abc] [def] [ghi]
var valueString = obj.ToString();
var valueSplitArray = valueString.Split(',');
for (int k = 0; k < valueSplitArray.Count(); k++)
{
string listValue = "";
if (k != valueSplitArray.Count() - 1)
{
var startIndex = valueSplitArray[k].IndexOf("\"");
listValue = valueSplitArray[k].Substring(startIndex + 1, valueSplitArray[k].Length - startIndex - 2);
}
else
{
var startIndex = valueSplitArray[k].IndexOf("\"");
listValue = valueSplitArray[k].Substring(startIndex + 1, valueSplitArray[k].Length - startIndex - 5);
}
// each value is then added to the main list object
Console.WriteLine(#"{0}Obj.Add(""{1}"");", ToCamelCase(key), listValue);
SetOutput(#""+ToCamelCase(key)+#"Obj.Add("""+listValue+#""");");
}
Console.WriteLine("");
SetOutput("");
}
static void PrintKeyValuePairCase(string parentObj, string key, string value)
{
Console.WriteLine("{0}Obj.{1} = \"{2}\";", ToCamelCase(parentObj), key, value);
SetOutput("" + ToCamelCase(parentObj) + "Obj." + key + " = \"" + value + "\";");
}
static string ToCamelCase(string str)
{
if (!String.IsNullOrEmpty(str))
{
return str.Substring(0, 1).ToLower() + str.Substring(1, str.Length - 1);
}
else
{
return null;
}
}
static string MappedClass(string str)
{
if (classMap.ContainsKey(str))
return classMap[str];
else
return str;
}
static void InitializeClassMap()
{
classMap.Add("employeeInformation", "EmployeeInfo");
}
static void SetOutput(string str)
{
using (System.IO.StreamWriter file = new System.IO.StreamWriter(outputFilePath, true))
{
file.WriteLine(str);
}
}
}
}

Categories