I've got a json file containing
{
"Accounts": null,
"AccountTypes": null,
"Actions": null,
"Photos": [
{
"Instance": "...",
"Key": "..."
},
....
]
}
Now I want to get all the Instance properties from the Photo objects. I've got the following code:
var photos = new List<Photo>();
string json = File.ReadAllText(file);
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json, typeof(object));
var jsonPhotos = jsonObj.Photos as IEnumerable<dynamic>;
var instances = jsonPhotos.Select(x => x.Instance);
foreach (var instance in instances)
photos.Add(new Photo
{
Document = Convert.FromBase64String(instance)
});
However, jsonPhotos.Select(x => x.Instance); isn't returning anything...
I am able to get things working by using
var instances = new List<string>();
foreach (var photo in jsonPhotos)
instances.Add(photo.Instance.Value);
But can I solve this in a LINQ way?
Why just don't use Json.Linq for that? Parse JSON to JObject instance, then map every token from Photos array to Photo instance (I've omitted Convert.FromBase64String because OP sample doesn't have a valid base64 data, but converting Instance value can be easily added)
var json = JObject.Parse(jsonString);
var photos = json["Photos"]
.Select(token => new Photo
{
Document = token["Instance"]?.Value<string>()
})
.ToList();
The .Select(x => x.Instance) indeed returns ... on .NET Core 3.1. Can you verify that the contents of the json variable are actually what you expect?
Specifically
jsonPhotos.Select(x => x.Instance);
works as expected, while
jsonPhotos.Select(x => x.SomeNonExistingProperty);
enumerates nothing / empty values.
For example, this code prints Instance A, then Instance B, then nothing twice:
var json = #"
{
""Photos"": [
{
""Instance"": ""Instance A"",
""Key"": ""...""
},
{
""Instance"": ""Instance B"",
""Key"": ""...""
}]
}";
var jsonObj = JsonConvert.DeserializeObject<dynamic>(json);
var jsonPhotos = jsonObj.Photos as IEnumerable<dynamic>;
var instances = jsonPhotos.Select(x => x.Instance);
foreach (var instance in instances)
{
Console.WriteLine(instance);
}
// In contrast, this one will print empty lines.
instances = jsonPhotos.Select(x => x.SomeNonExistingProperty);
foreach (string instance in instances)
{
Console.WriteLine(instance);
}
I took the liberty to change the deserialization to dynamic directly, but it also works with the original code from the question.
Related
Given the following JSON
{
"enabled": true,
"name": "Name",
"description": "Test",
"rules": [
{
"propA": "a",
"propB": "b"
}
]
}
Is it possible in C# to deserialize on selected properties based on an input list:
var propertiesToInclude = new List<string> { "description", "rules.PropA" };
The example json is a simplified example, the real one can contain hundred of properties. The use case is to only return the fields that matches the input list in a dynamic or anonymous object and discard the other properties.
using Newtonsoft.Json.Linq;
var propertiesToInclude = new List<string> { "description", "rules.PropA" };
var splitted = propertiesToInclude.SelectMany(x => x.Split('.'));
string text = File.ReadAllText("test.json");
var json = JToken.Parse(text);
Process(json);
Console.WriteLine(json);
void Process(JToken token)
{
if (token is JObject jObject)
{
jObject.Properties()
.Where(x => !splitted.Contains(x.Name, StringComparer.OrdinalIgnoreCase))
.ToList()
.ForEach(x => x.Remove());
foreach (var x in jObject)
Process(x.Value);
}
else if (token is JArray jArray)
{
foreach (var x in jArray)
Process(x);
}
}
This code on the data shown will give the desired result.
The output is a JToken object containing the desired properties.
However, I used a simple search for all occurrences of names in a splited array. This will give false positives if, for example, the root object contains the propA property or the object in the array contains the description property.
To avoid this, you need to compare the JToken.Path property with the propertiesToInclude elements, taking into account the depth.
I'm getting tired that I can't figure out(nor alone nor through SO) how to pull out deeply nested data within BsonDocument or custom classes directly. I know that I should use for sure a filter and a projection to get out an array/list of Guids nested within another array.
Following is the structure(simplified):
//Thread
{
Id: "B2",
Answers: [
{
Id: "A1",
Likes: [ "GUID1", "GUID2", "ETC" ] //<- this array, and only this.
}
]
}
I have both the Thread.Id and Answer.Id as filtering data but then I tried with:
var f = Builders<BsonDocument>.Filter;
var filter = f.And(f.Eq("Id", ids.ThreadId), f.Eq("Answers.$[].Id", ids.AnswerId));
var projection = Builders<BsonDocument>.Projection.Include("Answers.Likes.$");
var likes = await dbClient.GetCollection<BsonDocument>(nameof(Thread))
.Find(filter)
.Project(projection)
.FirstOrDefaultAsync();
But this query always returns null, what I'm doing wrong from this POV?
It is not possible to project the individual fields from array in projection using regular queries.
You can at best project the matching element using regular queries and then map the likes.
Something like
var f = Builders<BsonDocument>.Filter;
var filter = f.And(f.Eq("Id", ids.ThreadId), f.Eq("Answers.Id", ids.AnswerId));
var projection = Builders<BsonDocument>.Projection.Include("Answers.$");
var answer = await dbClient.GetCollection<BsonDocument>(nameof(Thread))
.Find(filter)
.Project(projection)
.FirstOrDefaultAsync();
Alternatively you can use filters with map using aggregation to match the answer element by id followed by projection to map the like field.
Something like
var f = Builders<BsonDocument>.Filter;
var match = f.And(f.Eq("Id", ids.ThreadId), f.Eq("Answers.Id", ids.AnswerId));
var project = new BsonDocument("newRoot",
new BsonDocument("$arrayElemAt", new BsonArray {
new BsonDocument("$map",
new BsonDocument
{
{ "input",
new BsonDocument("$filter", new BsonDocument
{
{ "input", "$Answers"},
{"cond", new BsonDocument("$eq", new BsonArray { "$$this.Id", ids.AnswerId})}
})
},
{ "in", new BsonDocument("Likes", "$$this.UserLikes") }
}),
0}));
var pipeline = collection.Aggregate()
.Match(match)
.AppendStage<BsonDocument, BsonDocument, BsonDocument>(new BsonDocument("$replaceRoot", project));
var list = pipeline.ToList();
Working example here - https://mongoplayground.net/p/wM1z6q92_mV
I wasn't able to fetch Likes with single filtering and projection. However, I was able to achieve it by using aggregation pipeline.
private async Task<BsonArray> GetLikes(string docId, string answerId)
{
var client = new MongoClient();
var idFilter = Builders<BsonDocument>.Filter.Eq("ID", docId);
var answerIdFilter = Builders<BsonDocument>.Filter.Eq("Answers.ID", answerId);
var projection = Builders<BsonDocument>.Projection.Exclude("_id").Include("Answers.Likes");
var likes = await client.GetDatabase("test").GetCollection<BsonDocument>("items")
.Aggregate(new AggregateOptions())
.Match(idFilter)
.Unwind("Answers")
.Match(answerIdFilter)
.Project(projection)
.FirstOrDefaultAsync();
return likes == null ? null
: (likes.GetElement("Answers").Value as BsonDocument).GetElement("Likes").Value as BsonArray;
}
For some reason result included document in original structure as opposed to including just a document with Likes property so I had to do some post processing afterwards.
I am trying to parse Neo4jClient's query result of paths (of type string) using JsonConvert.
I was able to parse the query result for nodes using this method:
var _gClient = new GraphClient(new Uri("http://localhost:7474/db/data"));
_gClient.Connect();
var result = _gClient.Cypher
.Match("(n)")
.Return(n => n.As<string>())
.Results.ToList();
result.ForEach(n =>
{
var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(n);
Console.WriteLine("Keys: " + String.Join(",", dict.Keys));
Console.WriteLine("Values: " + String.Join(",", dict.Values));
});
However, when I tried to do the same with the query result of paths, JsonConvert's DeserializeObject method can't do the same thing:
var _gClient = new GraphClient(new Uri("http://localhost:7474/db/data"));
_gClient.Connect();
var result = _gClient.Cypher
.Match("p=(a)-[:ACTED_IN]->(m:Movie {title:'The Matrix'})")
.Return(p => new
{
Nodes = Return.As<IEnumerable<string>>("EXTRACT(p in nodes(p) | p)"),
Relationships = Return.As<IEnumerable<string>>("EXTRACT(p in relationships(p) | p)")
})
.Results.ToList();
foreach (var n in result)
{
foreach (var s in n.Nodes)
{
JsonConvert.DeserializeObject<Dictionary<string, string>>(s);
}
}
The error was Unexpected character encountered while parsing value: {. Path 'extensions', line 2, position 17.
This is part of the string to be parsed:
{
"extensions": {},
"metadata": {
"id": 8,
"labels": [
"Person"
]
},
Does that mean Json can't deserialize empty braces? If so is there any other way I can parse this huge structure out?
The problem is that you're trying to deserialize into a Dictionary<string,string> but your JSON isn't a Dictionary<string,string> the error about the unexpected { is because for it to be a Dictionary<string,string> the JSON would look like:
{
"Key1" : "Value1",
"Key2" : "Value2"
}
The { on both extensions and metadata imply that there is an object there, not just a string. To that end you can deserialize it using something like:
JsonConvert.DeserializeObject<Dictionary<string, object>>(s)
But if you want to access the properties, you may as well do:
JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(s)
I have json file like this:
{
"fields": {
"customfield_10008": {
"value": "c1"
},
"customfield_10009": {
"value": "c2"
}
...
}
}
and I would like to create dictionary in c# like:
key: value
"customfield_10008":"c1"
"customfield_10009":"c2"
How I can achive this? I load json in this way,
dynamic json = JsonConvert.DeserializeObject(File.ReadAllText("data.json");
and don't know how to create dict like above
A little bit linq tricks can help you
var dict = JObject.Parse(File.ReadAllText("data.json"))["fields"]
.Cast<JProperty>()
.ToDictionary(x => x.Name, x => (string)x.Value["value"]);
Come through the values and collect them:
var result = new Dictionary<string, string>();
foreach (var field in obj.fields)
{
result.Add(field.Name, Convert.ToString(field.Value.value));
}
If you have json which do not have type in compile time, you can use dynamic type at that time.
I would parse above json using dynamic type and generate dictionary with parsed value :
var dicValues = new Dictionary<string,string>(); // this dictionary contains key value pair result
dynamic res = JsonConvert.DeserializeObject<dynamic>(File.ReadAllText("data.json");
dynamic availableFields = res["fields"];
if (availableFields != null)
{
foreach (var field in availableFields)
dicValues.Add(field.Name, field.Value["value"].Value);
}
I have a problem on data parsing, I am using C# in Visual Studios and I need a parsing algorithm for my json file. This is the structure:
{
"objects": {
"minecraft/sounds/mob/stray/death2.ogg": {
"hash": "d48940aeab2d4068bd157e6810406c882503a813",
"size": 18817
},
"minecraft/sounds/mob/husk/step4.ogg": {
"hash": "70a1c99c314a134027988106a3b61b15389d5f2f",
"size": 9398
},
"minecraft/sounds/entity/rabbit/attack2.ogg": {
"hash": "4b90ff3a9b1486642bc0f15da0045d83a91df82e",
"size
I want to pull "minecraft/sounds/mob/stray/death2.ogg" and "hash" the data.
My C# code:
HttpWebRequest reqobje = WebRequest.Create(assetsurl) as HttpWebRequest;
using (HttpWebResponse response = reqobje.GetResponse() as HttpWebResponse)
{
StreamReader objejsonsr = new StreamReader(objectjson);
jsonVerisi = objejsonsr.ReadToEnd();
}
parser = JObject.Parse(jsonVerisi);
JToken job = parser["objects"];
Since you're using json.net, you can deserialize the string into any object you need. The sample below is an anonymous type with dictionary so you can use the dynamic keys that are coming back:
var result = JsonConvert.DeserializeAnonymousType(jsonVerisi, new { objects =
new Dictionary<string, Dictionary<string, string>>() });
var objects = result.objects; // key/value;
This is one way you can use it (maybe even to map to your own model instead of anonymous types to make it easier to work with):
var objects = result.objects
.Select(m => new
{
Path = m.Key,
Hash = m.Value["hash"],
Size = int.TryParse(m.Value["size"], out var value) ? value : 0,
}).ToList();
var path = objects[0].Path; // Get the path of the first object