I'm fairly new to parsing Json with C# and i'm having a little issue i can't work my head around.
My data looks something like this:
{
"languages": {
"ja_lang": {
"data": {
"name": "Japanese"
},
"files": [["ja",
"Japanese File",
"lang_ja.txt"]]
},
"en_lang": {
"data": {
"name": "English"
},
"files": [["en",
"English File",
"lang_en.txt"]]
}
}
}
Now i want to iterate over the items in languages and only work with the one where the object-name starts with "ja_" (in this case it would only work with "ja_lang" and ignore "en_lang"), then extract the name inside data and the "lang_ja.txt" in files.
To Parse the Json in C# i downloaded the Newtonsoft.Json library and came up with this:
dynamic json_obj = JsonConvert.DeserializeObject("json string");
// when debugging language holds { "ja_lang": { "data": { "name": "Japanese" }, "files": [["ja", "Japanese File", "lang_ja.txt"]] } }
foreach (var language in json_obj.languages)
{
// not sure how i can access the object-name
/*if(!language.StartsWith("ja_"))
continue;*/
// Exception: 'Newtonsoft.Json.Linq.JProperty' does not contain a definition for 'data' - Not sure why it is treated as a property?
var name = language.data.name;
var file = language.files[2];
}
I'm sorry for this probably dumb question, but i've been trying to cast it to different types and searched the web for solutions, but i just couldn't figure it out. So if someone could help me out with this i would be really greatful.
Thanks in advance!
Since you're stating in a comment (on an answer that has been deleted) that the data changes so a fixed model won't work, you can still fix what is known:
Here's a LINQPad program that demonstrates:
void Main()
{
var collection = JsonConvert.DeserializeObject<LanguagesCollection>(File.ReadAllText(#"c:\temp\test.json"));
foreach (var keyValuePair in collection.Languages)
if (keyValuePair.Key.StartsWith("ja_"))
keyValuePair.Value.Dump();
}
public class LanguagesCollection
{
public Dictionary<string, JObject> Languages { get; } = new Dictionary<string, JObject>();
}
This will deserialize the outer object, with the "languages" key, and inside you have a dictionary with the keys, "ja_lang", "en_lang", and you can just process the values as you see fit. These are left as JObject which means they will contain whatever json was present as a value for that key in the dictionary.
Using a site like json2sharp you can just pass your json data in and get a ready to use c# model out.
Then you can easily deserialize your json data into that c# model and use the properties for much easier handling:
string jsonData = #"{
'languages': {
'ja_lang': {
'data': {
'name': 'Japanese'
},
'files': [['ja',
'Japanese File',
'lang_ja.txt']]
},
'en_lang': {
'data': {
'name': 'English'
},
'files': [['en',
'English File',
'lang_en.txt']]
}
}
}";
RootObject data = JsonConvert.DeserializeObject<RootObject>(jsonData);
foreach(Languages lang in data.languages) //would work if Languages was a listing
{
}
Although I admit that your Json is a bit strange and that Languages most likly should be a listing and not a property for each language.
Related
I have an application attached to the configuration file:
{
"ProjectModules": [
{
"Version": "1",
"LoginModule": {
"LoginLogic": "Project1.ModulesV1.LoginModule.Logic.LoginLogic"
}
},
{
"Version": "2",
"LoginModule": {
"LoginLogic": "Project1.ModulesV2.LoginModule.Logic.LoginLogic"
}
}
]
}
How to get the value for the "LoginLogic" key and for a specific version?
Here I started to do but it does not take into account that it is a data table
if (_configuration.GetSection("ProjectModules:" + moduleName).Exists())
{
var configSection = _configuration.GetSection("ProjectModules:" + moduleName);
if (configSection[sectionName] != null)
{
part = configSection[sectionName];
}
}
EDIT:
moduleName -> LoginModule
sectionName -> LoginLogic
I need to get the value for the "LoginLogic" key knowing the version "Version"
It's going to be extremely difficult, if not impossible, to do what you want with JSON formatted this way. You need to understand how the configuration system works. No matter what the config source (JSON, environment variables, console arguments, etc.) everything, and I mean everything ends up dumped into a dictionary. Pretty much the entire responsibility of a config provider is to take the source and convert it into a dictionary, which is then returned and merged into the main configuration dictionary.
As such, what you're actually creating here is:
["ProjectModules[0]:Version"] = 1
["ProjectModules[0]:LoginModule:LoginLogic"] = "Project1.ModulesV1.LoginModule.Logic.LoginLogic"
["ProjectModules[1]:Version"] = 2
["ProjectModules[1]:LoginModule:LoginLogic"] = "Project1.ModulesV2.LoginModule.Logic.LoginLogic"
As you can see, there's no real way here to tell exactly which version belongs to which LoginLogic, except for the index of ProjectModules being the same. However, since that's just a string serving as a key in the dictionary, it's not something you can easily filter or search on.
One option would be to change the format a bit if you can. For example, if you instead had JSON like:
{
"ProjectModules": {
"Version1": {
"LoginModule": {
"LoginLogic": "Project1.ModulesV1.LoginModule.Logic.LoginLogic"
}
},
"Version2": {
"LoginModule": {
"LoginLogic": "Project1.ModulesV1.LoginModule.Logic.LoginLogic"
}
}
}
}
Then, you'd end up with:
["ProjectModules:Version1:LoginModule:LoginLogic"] = "Project1.ModulesV1.LoginModule.Logic.LoginLogic"
["ProjectModules:Version2:LoginModule:LoginLogic"] = "Project1.ModulesV2.LoginModule.Logic.LoginLogic"
And, it's easy enough to distinguish then by version.
I'm currently trying to create a small launcher to solve some problems using the existing launcher from minecraft.
I'm trying to read a .json file to get all the informations that i need.
If you need to take a look at the .json file here.
I got it working if i just need a single information like
string clienturl = readJson("//downloads/client/url");
with this:
private string readJson(string element)
{
string json = File.ReadAllText(Path.Combine(appPath + "1.10.2.json"));
var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(json), new System.Xml.XmlDictionaryReaderQuotas());
var root = XElement.Load(jsonReader);
return root.XPathSelectElement(element).Value;
}
The problem now is that i need to get informations for all the other files.
The "element" would be:
libraries/downloads/artifact/path
libraries/downloads/artifact/url
but obviously there is more then one entry for "path" and "url" so i need a foreach loop.
What do i need to change in my code above to make it working with a foreach loop?
Sorry for my bad english, i hope its not to hard to understand.
Small preview of the .json in case you dont want to download the file:
"libraries": [
{
"name": "com.mojang:netty:1.6",
"downloads": {
"artifact": {
"size": 7877,
"sha1": "4b75825a06139752bd800d9e29c5fd55b8b1b1e4",
"path": "com/mojang/netty/1.6/netty-1.6.jar",
"url": "https://libraries.minecraft.net/com/mojang/netty/1.6/netty-1.6.jar"
}
}
},
{
"name": "oshi-project:oshi-core:1.1",
"downloads": {
"artifact": {
"size": 30973,
"sha1": "9ddf7b048a8d701be231c0f4f95fd986198fd2d8",
"path": "oshi-project/oshi-core/1.1/oshi-core-1.1.jar",
"url": "https://libraries.minecraft.net/oshi-project/oshi-core/1.1/oshi-core-1.1.jar"
}
}
},
{
"name": "net.java.dev.jna:jna:3.4.0",
"downloads": {
"artifact": {
"size": 1008730,
"sha1": "803ff252fedbd395baffd43b37341dc4a150a554",
"path": "net/java/dev/jna/jna/3.4.0/jna-3.4.0.jar",
"url": "https://libraries.minecraft.net/net/java/dev/jna/jna/3.4.0/jna-3.4.0.jar"
}
}
}
]
The issue is that your JSON contains an array, and it's not immediately obvious how JsonReaderWriterFactory maps an array to XML elements, given that XML doesn't have the concept of an array.
One way to determine this is to read through the documentation Mapping Between JSON and XML which describes this mapping. But another is simply to determine for ourselves, by using one of the answers from Get the XPath to an XElement? to find out the actual paths for each element. Using this answer, the following code:
var paths = root.DescendantsAndSelf().Select(e => e.GetAbsoluteXPath()).ToList();
Debug.WriteLine(String.Join("\n", paths));
Produces output like:
/root/libraries
/root/libraries/item[1]
/root/libraries/item[1]/name
/root/libraries/item[1]/downloads
/root/libraries/item[1]/downloads/artifact
/root/libraries/item[1]/downloads/artifact/size
/root/libraries/item[1]/downloads/artifact/sha1
/root/libraries/item[1]/downloads/artifact/path
/root/libraries/item[1]/downloads/artifact/url
/root/libraries/item[2]
/root/libraries/item[2]/name
/root/libraries/item[2]/downloads
/root/libraries/item[2]/downloads/artifact
/root/libraries/item[2]/downloads/artifact/size
/root/libraries/item[2]/downloads/artifact/sha1
/root/libraries/item[2]/downloads/artifact/path
/root/libraries/item[2]/downloads/artifact/url
So, as you can see, each array item is placed in a synthetic <item> node.
Thus you can query your paths and urls as follows:
var files = root
.XPathSelectElements("libraries/item/downloads/artifact")
.Select(e => new PathAndUrl { Path = (string)e.Element("path"), Url = (string)e.Element("url") })
.ToList();
Placing the result into a list of the following class:
public class PathAndUrl
{
public string Path { get; set; }
public string Url { get; set; }
}
With the following code:
static void Main(string[] args)
{
JObject fileSystemTree = CreateFileSystemJsonTree("C:/DirectoryTree");
Console.WriteLine(fileSystemTree);
Console.WriteLine("------");
//
// Write it out to a file
//
System.IO.File.WriteAllText(#"C:\jsontree.txt", fileSystemTree.ToString());
Console.ReadLine();
}
static JObject joNew = new JObject();
static JObject CreateFileSystemJsonTree(string source)
{
//
// Build a list of all the IPs
//
//Console.WriteLine(source);
using (var poiDbContext = new poiDbEntities())
{
DirectoryInfo di = new DirectoryInfo(source);
{
joNew = new JObject(
new JProperty(di.Name, new JArray(Directory.GetDirectories(source).Select(d => CreateFileSystemJsonTree(d)))),
new JProperty("files", new JArray(di.GetFiles().Select(fi => new JObject(new JProperty(fi.Name, GetText(fi.Name)))))));
}
Console.WriteLine(joNew);
}
return joNew;
}
public static string GetText(string fiName)
{
using (var poiDbContext = new poiDbEntities())
{
string indexNameBody = fiName.Substring(0, fiName.LastIndexOf('.'));
var indexResult = "test"; // dummied up for use by evaluators
return indexResult.Trim();
}
}
I am trying to create a filetree from a system directory using recursion. I have this working with XML but would prefer a JSON tree. The problem is that the txt files appear, not withing the Parent's [], which represents the lowest folder, but instead being added as JsonProperties with the text name I have added as "files"(which I don't want). Plus the "files" are generated even if there are no files, such as in an empty folder. The system generated snippet is followed by the a possible desired snippet.
"Sports": [
{
"NBA": [],
"files": [] // Undesirable--The folder is empty``
},``
The two snippets:
{
"Politics": [
{
"PresCandidates": [
{
"Republican": [], // Notice the files are not in array within the [] of the Republican
"files": [
{
"carson_ben_s.txt": "Ben Carson"
},
{
"trump_donald_j.txt": "Donald Trump"
},
{
"walker_scott_k.txt": "Scott Walker"
}
]
}
]
}
],
"Politics": [ // The desired format
{
"PresCandidates": [
{
"Republican": [
{
"carson_ben_s.txt": "Ben Carson"
},
{
"trump_donald_j.txt": "Donald Trump"
},
{
"walker_scott_k.txt": "Scott Walker"
}
],
{
I'm not sure I understand the JSON you want to create. If you're looking for a set of nested JSON objects where each property name corresponds to the file or directory name, and for a file, the property value is given by some callback method, while for a subdirectory, the value is an object recursively containing objects for all contents of the subdirectory, you could do this:
public static class DirectoryInfoExtensions
{
public static JObject ToJson<TResult>(this DirectoryInfo info, Func<FileInfo, TResult> getData)
{
return new JObject
(
info.GetFiles().Select(f => new JProperty(f.Name, getData(f))).Concat(info.GetDirectories().Select(d => new JProperty(d.Name, d.ToJson(getData))))
);
}
}
And then use it like:
string path = #"C:\Program Files (x86)\Microsoft Visual Studio 9.0";
var di = new DirectoryInfo(path);
Debug.WriteLine(di.ToJson(f => f.LastWriteTimeUtc));
Which produces output like:
{
"redist.txt": "2007-10-16T21:56:34Z",
"Common7": {
"IDE": {
"Brief.vsk": "2007-06-20T21:55:14Z",
"WinFxCustomControlTemplateWizard.dll": "2008-07-30T14:06:58Z",
"1033": {
"cmddefui.dll": "2008-07-30T14:06:58Z",
"Microsoft.VisualStudio.DesignUI.dll": "2008-07-30T14:06:58Z",
"Microsoft.VisualStudio.EditorsUI.dll": "2008-07-30T14:06:58Z",
Many values removed,
"VsWizUI.dll": "2008-07-30T14:06:58Z",
"WindowsFormsIntegration.PackageUI.dll": "2008-07-30T14:06:58Z"
},
"en": {
"Microsoft.VisualStudio.Package.LanguageService.xml": "2007-03-02T04:30:40Z",
"Microsoft.VisualStudio.Shell.Design.xml": "2007-03-06T04:40:44Z",
"Microsoft.VisualStudio.Shell.xml": "2007-03-06T04:40:44Z"
},
"ExceptionAssistantContent": {
"1033": {
"DefaultContent.xml": "2007-09-03T05:11:44Z"
}
}
}
}
}
Is that what you want? Your partial JSON sample has arrays but I don't see where or how you need them.
If this is not what you want, could you clarify the desired JSON format somewhat?
By the way, if you have XML working, you could always use JsonConvert.SerializeXNode().
You idea worked beautifully. Thank you. I have one comment and a further question. I understand there is no standard format for a json file tree. However doesn't it make sense that each folder should be started and ended with [] because they are not "leaf" nodes? I tried to modify you code to make it look like the following but without success. I added the time stamp.
{
"7/28/2015 6:00:45 PM": // Modified by hand
lennane_ava_l.txt": "Ava Lennane",
"ALists": [ // Modified by hand
"clinton_hillary_r.txt": "Hillary Clinton",
"depasquale_vincent_99.txt": "Vincent Depasquale",
"trump_donald_j.txt": "Donald Trump",
"zz_kilroy_99.txt": "Kilroy",
"Criminals": [], // Modified by hand
"Entertainment": [], // Modified by hand
"Politics": [ // Modified by hand
"clinton_hillary_r.txt": "Hillary Clinton",
"lennane_james_p.txt": "Jim Lennane",
"trump_donald_j.txt": "Donald Trump",
"PresCandidates": { // Unmodified
"clinton_hillary_r.txt": "Hillary Clinton",
"trump_donald_j.txt": "Donald Trump",
"Democrat": { // Unmodified
"clinton_hillary_r.txt": "Hillary Clinton"
},
"Other": {},
"Republican": {
"carson_ben_s.txt": "Ben Carson",
"lennane_james_p.txt": "Jim Lennane",
"trump_donald_j.txt": "Donald Trump",
"walker_scott_k.txt": "Scott Walker"
I have json file Users.json and I want delete one element. This is the structure:
{
"1": {
"Username": "1",
"Name": "1",
"AccessGroup": "Administrators"
},
"2": {
"Username": "2",
"Name": "2",
"AccessGroup": "Supervisors"
},
"3": {
"Username": "3",
"Name": "3",
"AccessGroup": "Administrators"
}
}
And the code:
public void DeleteUser(Users.User user)
{
String filename = USERS_PATH;
try
{
if (File.Exists(filename))
{
var data = DeserializeFromFile<Dictionary<String, User>>(USERS_PATH);
foreach (var item in data)
{
if (user.Username == data[item.Key].Username)
{
data.Remove(user.Username);
break;
}
}
}
}
catch (Exception)
{
throw new ArgumentException("User has not deleted");
}
}
After that iteration the file is the same like before. Where do I wrong? Thanks in advance.
There are a few things wrong with this code:
After loading the file into an object, the Dictionary<string,User> no longer corresponds to the file: if the dictionary is updated, the file isn't and vice versa, you you need to serialize the data again and save it to the file with:
String encoded = JsonConvert.SerializeObject(data);
System.IO.File.WriteAllText(filename,encode);
Dictionary.Remove asks to provide a key, you can remove with the username, it's not guaranteed (especially not in your example file), that the key is the username. The result is that nothing is removed. So you should use:
data.Remove(item.Key);
instead of:
data.Remove(user.Username);
Next, you shouldn't remove data in an iterator. Although this works in this case because you do a break, in general it's a very bad idea to do this since most enumerators are not designed to enumerate over changing collections. In this case you can store a reference to the key to be removed:
var torem = null;
foreach (var item in data) {
if (user.Username == data[item.Key].Username) {
torem = item.Key;
break;
}
}
if(torem != null) {
data.Remove(torem);
}
You can also save some CPU cycles by using item.Value instead of data[item.Key].Username since what you do is a lookup for a value where you already have the pointer so use:
if(user.Username == item.Value.Username)
instead of:
if (user.Username == data[item.Key].Username)
You have to save the data to he same file.
File.WriteAllText(USERS_PATH, JsonConvert.SerializeObject(data));
// I don't know what JSON parser you're using, but replace it with
// whatever that library provides for serializing data/saving to file :)
or something like this :)
Basically you're working on an in-memory copy of what was read from file. If you want to use a JSON like a DB, you might want to write some kind of DAO/Repository that will just expose basic CRUD methods (open file -> perform changes -> write file).
I could be wrong, but don't you have to delete an item by its key, not by a property of the item?
So:
data.Remove(item.Key);
If I have json that looks something like this: (wrote this by hand, so it may have errors)
{
"http://devserver.somesite.com/someendpoint/1/2/3$metadata#Something.Start": [
{
"Title": "start",
"Endpoint": "https://devserver.somesite.com/someendpoint/1/2/3/start"
}
],
"http://devserver.somesite.com/someendpoint/1/2/3$metadata#Something.Stop": [
{
"Title": "stop",
"Endpoint": "https:// devserver.somesite.com/someendpoint/1/2/3/stop"
}
]
}
Is there any easy, built in way (JSON.net) to have it understand that there’s a namespace in play here? Or is there a way to set a variable or pattern based JsonProperty via an attribute?
I can't have the URL as part of my business object, because that will change from environment to environment.
I know I can create a custom json converter, but before going down that route I’d like to see if there’s something more out of box that handles this. Another option is to get the data via xml and handle that by hand.
Assuming you are taking this as a string that you have received from a web call you can do the following in JSON.NET.
var json = "your string here";
var obj = JObject.Parse(json);
foreach(var ns in obj.Properties) {
var arr = (JArray)ns.Value;
foreach(var obj2 in arr) {
// do you logic here to get the properties
}
}
Another option that James Newton-King provided you can do this, which seems a little cleaner:
var list = JsonConvert.DeserializeObject<Dictionary<string, List<MyClass>>>(json);