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"] }
Related
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;
}
I have to send a variables Json to Mailgun but it only accepts the curly braces format when using multi level json files. So,
How can I pass from this:
{ "vehicle.type": "car"}
To this, with C#
{"vehicle": {"type": "car"}}
Having into consideration that sometimes it could be up to 3 nested elements. Like element1.element2.element3: value
Here is what I recommend.
note: I am using the Newtonsoft.Json library available via Nuget, if you are using .NET Core, you can use the built in System.Text.Json library.
Because we have multiple properties in the object with flattened property keys, qualified with .s and we need to convert these properties into a hierarchical, nested JSON structure, merging siblings appropriately at each level, a simple string replacement is neither safe nor effective.
Therefore, the approach here will be to parse the flattened property keys, such as "hospital.hospitalExtraData1.Street" recursively inferring and creating a hierarchy of nested objects.
Let's begin
var originalJson = #"{
""hospital.Name"": ""BestOneEver"",
""hospital.Estatus"": ""Active"",
""hospital.hospitalExtraData1.Street"": ""43"",
""hospital.hospitalExtraData1.Color"": ""Blue"",
""hospital.hospitalExtraData1.hospitalExtraData2.IsExpensive"": ""No"",
""hospital.hospitalExtraData1.hospitalExtraData2.Works24Hrs"": ""Yes"",
""patient.Name"": ""Leonel Messi"",
""patient.Age"": ""23""
}";
var original = JsonConvert.DeserializeObject<IDictionary<string, object>>(originalJson);
Now we have an object model we can work with and restructure.
We will do this using recursion
var original = JsonConvert.DeserializeObject<IDictionary<string, object>>(originalJson);
IDictionary<string, object> Expand(IDictionary<string, object> input)
{
var result = new Dictionary<string, object>();
foreach (var property in input)
{
var (key, remainder) = ParseKey(property.Key);
if (!result.ContainsKey(key))
{
result[key] = remainder != null
? Expand(new Dictionary<string, object>
{
[remainder] = property.Value
})
: property.Value;
}
else if (result[key] is IDictionary<string, object> inner)
{
inner[remainder] = property.Value;
result[key] = Expand(inner);
}
else
{
result[key] = property.Value;
}
}
return result;
}
(string key, string remainder) ParseKey(string key)
{
var dotIndex = key.IndexOf('.');
if (dotIndex != -1)
{
return (key.Substring(0, dotIndex), key.Substring(dotIndex + 1));
}
return (key, null);
}
var expanded = Expand(original);
var expandedJson = JsonConvert.SerializeObject(expanded, Newtonsoft.Json.Formatting.Indented);
Result:
{
"hospital": {
"Name": "BestOneEver",
"Estatus": "Active",
"hospitalExtraData1": {
"Street": "43",
"Color": "Blue",
"hospitalExtraData2": {
"IsExpensive": "No",
"Works24Hrs": "Yes"
}
}
},
"patient": {
"Name": "Leonel Messi",
"Age": "23"
}
}
Here is tricky way using string replacement.
replace for any dot(.) with this (": {") and add close tag (}) at the end for every dot(.) count. Good luck!
Try This:
IDictionary<string, object> Expand(IDictionary<string, object> d)
{
var result = new Dictionary<string, object>();
foreach (KeyValuePair<string, object> item in d)
{
var segments = item.Key.Split('.');
if (segments.Length > 1)
{
if (result.ContainsKey(segments[0]))
{
dynamic obj = new ExpandoObject();
obj = result[segments[0]];
IDictionary<string, object> myObj = obj;
myObj.Add(string.Join(".", segments.Skip(1)), item.Value);
result[segments[0]] = Expand(myObj);
}
else
{
result[segments[0]] = Expand(new Dictionary<string, object>
{
[string.Join(".", segments.Skip(1))] = item.Value
});
}
}
else
{
result[segments[0]] = item.Value;
}
}
return result;
}
I have data stored in a DB field acting as a cache from an API call.
This data is either of the type:
MyObject
List<MyObject>
I currently have the following code which uses NewtonSoft.JSON to take the string and (based on a successful deserialization) determine which of those types the string is.
var _lstObj = new List<MyObject>();
var _obj = new MyObject();
bool isList = false;
try
{
var n = JsonConvert.DeserializeObject<List<MyObject>>(cacheString);
if(n!=null && n.Count()>0)
{
_lstObj = n;
isList = true;
}
}
catch(Exception e)
{
isList = false;
}
if (!isList)
{
try
{
var x = JsonConvert.DeserializeObject<MyObject>(cacheString);
if (x != null && x.Data != null)
{
_obj = x;
}
}
catch(Exception e) { }
}
This feels kind of cumbersome...is there a better way to do this?
I'll always have a string for the incoming data.
It will only ever be 1 of those 2 types.
This should work:
var obj = (JToken)JsonConvert.DeserializeObject(cacheString);
if (obj.Type == JTokenType.Array)
{
var result = obj.ToObject<List<MyObject>>();
}
else
{
var result = obj.ToObject<MyObject>();
}
Checking first if the json string is a list should solve the problem.
if (cacheString.StartsWith("["))
{
var myList = JsonConvert.DeserializeObject<List<MyObject>>(cacheString);
}
else
{
var myObject = JsonConvert.DeserializeObject<MyObject>(cacheString);
}
I would use the expando object, that way you can keep adding properties if you like dynamically.
dynamic exp = new ExpandoObject();
try
{
exp.isList = true;
exp.value = JsonConvert.DeserializeObject<List<MyObject>>(cacheString);
}
catch (Exception e)
{
exp.isList = false;
exp.value = JsonConvert.DeserializeObject<MyObject>(cacheString);
}
but ideally I would cache the object as a List regardless if its one or many.
In JSON.Net I can read a JSON file into a data structure and then only convert the properties that I'm interested in to objects. For example, if I have a JSON file like
{
"Bob": {
"Steve": 5
}
}
I can get the object like so:
class SteveObject {
public int Steve;
}
var jsonSerializer = JsonSerializer.Create()
var jsonFile = JObject.Parse(text);
vat steveObject = jsonFile["Bob"].ToObject<SteveObject>(jsonSerializer)
How do I do the same thing with YAMLDotNet or SharpYaml? I.e. if I have a YAML file like
Bob:
Steve: 5
How do I reconstruct the SteveObject from that without having to create the outer object?
Ok, I've written some code using SharpYaml that takes a simplified JPath query and returns the requested object.
public static T ReadPath<T>(Serializer serializer, string yaml, string path) {
var eventReader = new EventReader(new Parser(new StringReader(yaml)));
var streamReader = new MemoryStream(Encoding.UTF8.GetBytes(path ?? ""));
if ((char)streamReader.ReadByte() != '$')
throw new Exception();
if (streamReader.Position == streamReader.Length)
return serializer.Deserialize<T>(eventReader);
eventReader.Expect<StreamStart>();
eventReader.Expect<DocumentStart>();
while (streamReader.Position < streamReader.Length) {
if ((char)streamReader.ReadByte() != '.')
throw new Exception();
eventReader.Expect<MappingStart>();
var currentDepth = eventReader.CurrentDepth;
var nextKey = ReadPropertyName(streamReader);
if (string.IsNullOrEmpty(nextKey))
throw new Exception();
while (eventReader.Peek<Scalar>() == null || eventReader.Peek<Scalar>().Value != nextKey) {
eventReader.Skip();
// We've left the current mapping without finding the key.
if (eventReader.CurrentDepth < currentDepth)
throw new Exception();
}
eventReader.Expect<Scalar>();
}
return serializer.Deserialize<T>(eventReader);
}
public static string ReadPropertyName(MemoryStream stream) {
var sb = new StringBuilder();
while (stream.Position < stream.Length) {
var nextChar = (char) stream.ReadByte();
if (nextChar == '.') {
stream.Position--;
return sb.ToString();
}
sb.Append(nextChar);
}
return sb.ToString();
}
Given the above YAML the following works:
var t = "Bob:\r\n Point: [1.0, 2.0, 3.0]\r\n Steve: 5\r\n";
var serializer = new Serializer(settings);
var s = ReadPath<SteveObject>(serializer, t, "$.Bob"); // { "Steve": 5 }
var s2 = ReadPath<int>(serializer, t, "$.Bob.Steve"); // 5
Sure wish there were a library for this, though.
Is there anything built into .NET 4.5 that will generate a string C# POCO from a dynamic with all auto-implemented properties?
If not, is there anything built into .NET that will give you (something like a) List<KeyValuePair<string, Type>> so that we can generate a POCO according to the pseudo-code:
foreach (var kvp in list)
{
builder.AppendFormat("public {0} {1} {{ get; set; }}", kvp.Value, kvp.Key);
}
Finally, are there any well-known libraries that can assist with this sort of very basic code generation?
You can use compileassemblyfromsource to compile your string,
http://msdn.microsoft.com/en-us/library/system.codedom.compiler.codedomprovider.compileassemblyfromsource(v=vs.110).aspx
var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v4.0" } });
var cp = new CompilerParameters()
{
GenerateExecutable = false,
GenerateInMemory = true
};
cp.ReferencedAssemblies.Add("mscorlib.dll");
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Core.dll");
// The string can contain any valid c# code
// A valid class need to be created with its own properties.
var s = "public class POCOClass{ public int ID {get {return 1;}} }";
// "results" will usually contain very detailed error messages
var results = csc.CompileAssemblyFromSource(cp, s);
var type = results.CompiledAssembly.GetType("POCOClass");
var obj = (dynamic)Activator.CreateInstance(type);
var output = obj.ID;
// or
var output_ = obj.GetType().GetProperty("ID").GetValue(obj, null);
You need to define a class for your properties such as called POCOClass.
EDIT:
public static T CopyObjectFromExpando<T>(this object s) where T : class
{
var source = (ExpandoObject)s;
// Might as well take care of null references early.
if (source == null)
{
throw new ArgumentNullException("s");
}
var propertyMap = typeof(T).GetProperties().ToDictionary(p => p.Name.ToLowerInvariant(), p => p);
var destination = Activator.CreateInstance<T>();
// By iterating the KeyValuePair<string, object> of
// source we can avoid manually searching the keys of
// source as we see in your original code.
foreach (var kv in source)
{
PropertyInfo p;
if (propertyMap.TryGetValue(kv.Key.ToLowerInvariant(), out p))
{
var propType = p.PropertyType;
if (kv.Value == null)
{
if (!propType.IsNullable() && propType != typeof(string))
{
// Throw if type is a value type
// but not Nullable<>
throw new ArgumentException("not nullable");
}
}
else if (propType.IsEnum)
{
var enumvalue = Enum.ToObject(propType, kv.Value);
p.SetValue(destination, enumvalue, null);
continue;
}
else if (propType == typeof(bool) && kv.Value.GetType() != typeof(bool))
{
var boolvalue = Convert.ToBoolean(kv.Value);
p.SetValue(destination, boolvalue, null);
continue;
}
else if (propType.IsNullable())
{
var nullType = Nullable.GetUnderlyingType(propType);
var value = Convert.ChangeType(kv.Value, nullType);
p.SetValue(destination, value, null);
continue;
}
else if (kv.Value.GetType() != propType)
{
// You could make this a bit less strict
// but I don't recommend it.
throw new ArgumentException("type mismatch");
}
p.SetValue(destination, kv.Value, null);
}
}
return destination;
}
ImpromptuInterface, open source on Nuget
PM> Install-Package ImpromptuInterface
Has ActLikeProperties
by using ImpromptuInterface
Impromput.ActLikeProperties(dynObj, list.ToDictionary(k=>k.Key,v=>v.Value))
This emits a poco dlr proxy around the dynObj
The intent was to be able to bridge simple dynamic objects (like expando) to old code that uses reflection. But generally it's better to do what ImpromptuInterface's main function, which is wrap dynamic objects with statically declared interfaces.