I'm having a hard time getting the parent key/property/attribute of my JSON objects using JSON.net. That is, I want the outermost property name, as a string, without knowing beforehand/visually what it is. I'm currently iterating over a set of KeyValuePair items and attempting to, for each of those, log out the parent from something that looks like
{"parentKey":
{
"Name": "Name",
"Id": "123",
"Other": null,
"nestedArr":
[
"idx0",
"idx1",
"idx2"
]
}
}
I've tried both keyValue.Value.Ancestors() and keyValue.Value.Parent. With the former, I'm getting what looks to be the function definition... I'm actually not sure what it is: Newtonsoft.Json.Linq.JToken+<GetAncestors>d_ _ 42. Completely beffuddled by that, because based on the usage examples I've scrounged up here, I'm using it to standard.
With the latter, I log out the entire object, or else what appears to be the entire preceding KeyValuePair, rather than just the string "parentKey", which is what I want. The JSON.net docs aren't the best as far as explicit usage examples and what to expect (or maybe it's just that being new to C#, I can't make sense of them), but in any case, I'm kind of unclear on why this is happening and how to accomplish what I want. This is what I'm trying:
foreach (var keyValue in jObjList[0]) //jObjList is a List<JObject> defined above
{
Console.WriteLine(keyValue.Value.Ancestors());
Console.WriteLine(keyValue.Value.Parent);
if (keyValue.Value.GetType() == typeof(JObject))//same block goes for if it's typeof(JArray)
{
Console.WriteLine(keyValue.Key);
}
}
Edit: in the JSON given, and within the loop defined above, for example, in order to get my parent keys (that's just what I'm calling them), my code simply says, if (keyValue.Value.GetType() == typeof(JObject), write keyValue.Key to the console, and the same goes for if getType() is a JArray. In either case, keyValue.Key is a parent key, if that makes sense. What I mean to say by this is that it is a property that points to another Array or Object. My issue is that, as I'm doing this loop recursively, when I get down to a nested Array or Object, my code has no way of realizing that, although there is a new "parent key" currently, like with nestedArr, for example, the parent key of nestedArr is still "parentKey".
the code is abridged, but that's the idea.
All clarifications and corrections are welcome and appreciated. Thanks.
You are seeing Newtonsoft.Json.Linq.JToken+<GetAncestors>d_ _ 42 for Console.WriteLine(keyValue.Value.Ancestors()) because Ancestors is an IEnumerable<T> whose evaluation is lazy, rather than an explicit collection. What you are seeing is the ToString() output of the not-yet-evaluated enumerable.
If what you want to do is to climb up the parent list of a given JToken and find the lowest parent that has a "parentKey" property, then get the value of that parentKey, then this is how you would do it:
JToken token = keyValue.Value; // Here I'm declaring JToken explicitly for clarity. Normally I would use var token = ...
var parentKey = token.AncestorsAndSelf() // Climb up the json container parent/child hierachy
.Select(p => p.SelectToken("parentKey")) // Get the "parentKey" property in the current parent (if present)
.FirstOrDefault(k => k != null); // Return the first one found.
Console.WriteLine(parentKey);
Update
To get the name of the JSON property highest in the JSON container hierarchy, you would do:
var name = token.AncestorsAndSelf() // Walk up the list of ancestors
.OfType<JProperty>() // For each that is a property
.Select(p => p.Name) // Select the name
.LastOrDefault(); // And return the last (topmost).
Update 2
If you're looking for the first property name that appears in a JSON file, you can do the following, using JContainer.DescendantsAndSelf():
var json = #"[{""parentKey"":
{
""Name"": ""Name"",
""Id"": ""123"",
""Other"": null,
""nestedArr"":
[
""idx0"",
""idx1"",
""idx2""
]
}
}]";
var root = (JContainer)JToken.Parse(json);
var name = root.DescendantsAndSelf() // Loop through tokens in or under the root container, in document order.
.OfType<JProperty>() // For those which are properties
.Select(p => p.Name) // Select the name
.FirstOrDefault(); // And take the first.
Debug.WriteLine(name); // Prints "parentKey"
(JContainer represents a JSON node that can contain child nodes, such as an object or array.)
Related
I have a class written for ProblemDetails. I switched from NewtonSoft to System.Text.Json. I am trying to get the list of "errors", which is a deserialized into the Extensions property, which is decorated with [JsonExtensionData].
I cannot figure out how to enumerate and read the "errors", which inevitable is a dictionary.
I get to the point where I can get the "errors" back a JsonElement, but from JsonElement there is not "j.GetJsonElement", just j.GetString(), j.GetInt(), etc.
How do I get the elements below "errors"?
[Fact]
public void DeserializeTest()
{
var json = "{\"type\":\"https://tools.ietf.org/html/rfc7231#section-6.5.1\",\"title\":\"One or more validation errors occurred.\",\"status\":400,\"traceId\":\"00-ccf1398eea1494aef2e3fa0f07a34899-09a93c2176a88981-00\",\"errors\":{\"BottomRightX\":[\"Bottom Right X must be greater than Top Left X.\"],\"BottomRightY\":[\"Bottom Right Y must be greater than Top Left Y.\"]}}";
var problemDetails = JsonSerializer.Deserialize<ProblemDetails>(json);
problemDetails.Should().NotBeNull();
problemDetails.Title.Should().NotBeNullOrEmpty();
problemDetails.Status.Should().Be(400);
problemDetails.Extensions.Should().NotBeNullOrEmpty();
var j = problemDetails.Extensions.Should().ContainKey("errors").WhoseValue;
j.ValueKind.Should().Be(JsonValueKind.Object);
// all above pass
var x = j.GetJsonElement() ????;
}
My best guest is you can create a customer deserialization and in the errors value include a property type, to use it in as part as the custom serialization. But its not recommended. If U can never use object in Json Serialization, only Strong Type Objects to avoid this kind situation.
At this moment you should expect either some value or a property. To process properties - use GetProperty/TryGetProperty which has out parameter of JElement type:
j.TryGetProperty("BottomRightY", out JElement bottomRightY).Should().BeTrue();
I have a JSON object:
{
""settings"": {
""general"": {
""database"": { ""type"": ""PostgreSql"" }
}
}
}
The absolute path of my JSON object would look like this: settings/general/database/type
I tried to get all the keys with the first solution of this question:
IList<string> keys = parent.Properties().Select(p => p.Name).ToList();
This didn't work for me. The keys list only contained the first key settings and hasn't got the other ones.
There is a path property which shows the path of the node you are in but it doesn't show the complete path of the JSON object.
How can I get the absolute path like in my example?
In your question you are asking how to get the path to "my JSON object", but your sample JSON actually contains four objects, nested. (Each object begins with { and ends with } in the JSON.) So the path will be different depending on which JSON object you are referring to. It looks like you currently have a reference to the outermost object which you are querying to get the names of its properties. But this won't give you the descendant properties, as you've seen. What you need is to get the Path from the innermost property.
So I think we can boil your question down to this:
Given some JSON, how do I get the full path(s) to the deepest value(s) within the hierarchy (i.e. the leaf nodes)?
You can do that with this LINQ-to-JSON query:
var obj = JObject.Parse(json);
var paths = obj.DescendantsAndSelf()
.OfType<JProperty>()
.Where(jp => jp.Value is JValue)
.Select(jp => jp.Path)
.ToList();
If you only want the first one, then replace .ToList() with .FirstOrDefault().
Note that the path(s) will be returned with dots as delimiters. If you would prefer slashes, then add .Replace('.', '/') to jp.Path within the Select() method call.
Working demo here: https://dotnetfiddle.net/lFXtEE
Your parent object only has one key and that is "settings". Its value is a json object. That object has only one key and that is "general". Its value is a json object. That object has only one key and that is "database". Its value is a json object. You are using nested objects so you have to be specific about 'wanting all the keys' of which object.
The SO answer you referenced is not working because it is only giving you all the keys in your root object as a list of strings.
What you want is a recursive way to get all the "keys" (JProperty.Name).
If you have your JProperty with Name = "type", let us call it JProperty typeProp;. Then typeProp.Parent will get you the JContainer containing ""type"" : ""PostgreSql"" and typeProp.Parent.Parent will get you a JProperty with Name = "database".
So something like this might help (beware, untested):
JToken current = typeProp;
string path = "";
while (current != null)
{
path = current.Name + "/" + path;
if(current.Parent != null) current = current.Parent.Parent;
}
This will leave you with an extra slash on the end like this:
settings/general/database/type/
which you can remove with:
char[] charsToTrim = {'/'};
path.trimEnd(charsToTrim)
I'm trying to Get a result from Elasticsearch 1.7.0 using NEST 1.7.1. My documents contain many fields, but I'm interested in only one of them. I would prefer to get a typed result representing a partial document.
I'm using something along the lines of this code:
var settings = new ConnectionSettings(new Uri(url)).SetDefaultIndex("MyIndex");
var client = new ElasticClient(settings);
var result = client.Get<MyDtoPartial>(g => g
.Type("myDocType")
.Id("abcdefg123456")
.Fields("doc.subids")
);
Where MyDtoPartial currently looks like this:
public class MyDtoPartial
{
[JsonProperty("doc.subids")]
public IList<string> SubIds { get; set; }
// Other properties of my documents are not mapped, in this
// context I only want the SubIds.
}
In the debugger I can drill into result.Fields and see that the first in that dictionary has a value rendered by the debugger along these lines:
{[doc.subids, [ "12354adsf-123fasd", "2134fa34a-213123" ...
I can also see the Elasticsearch request that was made, which was like this:
http://myserver:12345/MyIndex/myDocType/abcdefg123456?fields=doc.subids
And it returns this type of json:
{
"_index": "MyIndex",
"_type": "myDocType",
"_id": "abcdefg123456",
"_version": 1,
"found": true,
"fields": {
"doc.subids": ["12354adsf-123fasd",
"2134fa34a-213123",
"adfasdfew324-asd"]
}
}
So I have a feelling my request is okay, because that is the kind of response I'd expect.
However, my goal was to get an instance of MyDtoPartial with a fully populated SubIds property. However, the result doesn't seem to contain any kind of property of type MyDtoPartial.
I've gone through the Nest Get docs, which actually led to the above code.
What is the proper way to Get a proper typed single document with only some fields from Elastic with Nest?
If you mention .Fields(...), Source will always be null. If you remove .Fields(...), then Source should be of type MyDtoPartial and give you the desired results. The reason you still get Source as null may be because in the mapping of myDocType, _source field is disabled. Check the definition of myDocType by executing GET <index name>/_mapping/myDocType. If _source is disabled, there is no way Nest will give you a concrete object of MyDtoPartial in its response for this type.
If you have _source enabled but only want to fetch a subset of fields, then you can use source filtering instead of fields to specify which fields you want and which ones you do not want to be returned in the response.
var result = client.Get<MyDtoPartial>(g => g
.Type("myDocType")
.Id("abcdefg123456")
.SourceInclude("doc.subids")
);
Now result.Source will be an object of MyDtoPartial where all fields except SubIds will be null and SubIds will have the expected value.
We are trying to write a way to consume a JSON feed from a third party but unfortunately it's not well written, there appears to be no rules on what is sometimes there or not and also the structure changes depending on the key, I have no way of changing this we have to work with what we have. We know what we want based on certain keys but we are struggling to find them as the nested structure changes.
Is it possible to flatten the JSON down to a single List> that we can query and the key would be the fully qualified name including parents so the following JSON:
{
"employees": [
{ "firstName":"John" , "lastName":"Doe" },
{ "firstName":"Anna" , "lastName":"Smith" },
{ "firstName":"Peter" , "lastName":"Jones" }
]
}
Would become a list with keys like:
"employees.firstName" and a value of "John".
I think if we could get to this we could query the data for what we want, we have been trying to use dynamic objects but as I said the JSON changes and properties are missing sometimes and it's proving impossible to handle all scenarios.
If you don't know reliably the structure of a JSON but you are sure about property name ex "firstName". You can use the JSON.NET JObject and recursively loop through it and check if the property you are looking for exists in the current JToken.
The code should look something Like that, note the code I didn't compile it can have some typing mistakes:
private List<string> LoadFirstNames(string json)
{
JObject o = JObject.Parse(json);
List<string> firstNames = new List<string>();
foreach(var token in o.GetPropertyValues())
{
FindFirstName(token, firstNames);
}
return firstNames;
}
private void FindFirstName(JToken currentProperty, List<string> firstNamesCollection)
{
if(currentProperty == null)
{
return;
}
if(currentProperty["firstName"] != null)
{
firstNamesCollection.Add(currentProperty["firstName"]);
}
foreach(var token into currentProperty.Values())
{
FindFirstName(token , firstNamesCollection);
}
}
I'm going through another developer's code, which is shown below:
[XmlElement("AdminRecipient")] public AdminRecipient[] AdminRecipientCollection = new AdminRecipient[0];
public AdminRecipient this[ string type ]
{
get
{
AdminRecipient result = null;
foreach( AdminRecipient emailRecipient in AdminRecipientCollection )
{
if( emailRecipient.Type == type )
{
result = emailRecipient;
break;
}
}
return( result );
}
Can someone explain what's going to happen in this line?
public AdminRecipient[] AdminRecipientCollection = new AdminRecipient[0];
The XML file that contains all of the email recipients has about 5 email addresses. But by using [0], will the foreach loop return each of those email addresses?
I have a basic understanding of indexers, but I don't this. What does it do?:
public AdminRecipient this[ string type ]
At the end of the day, the problem here is that the application doesn't send out an email when all 5 recipients are in the xml file. If I replace the 5 addresses with just 1 email addresses, then I'm able to get the email (which leads me to believe that there's a logic issue somewhere here).
An indexer allows you to use a type with the same syntax as array access. One of the simplest examples would be List<T>:
List<string> x = new List<string>();
x.Add("Item 0");
x.Add("Item 1");
string y = x[0]; // "Item 0"
That use of x[0] is calling the indexer for List<T>. Now for List<T> the index is an integer, as it is for an array, but it doesn't have to be. For example, with Dictionary<TKey, TValue> it's the key type:
Dictionary<string, string> dictionary = new Dictionary<string, string>();
dictionary.Add("foo", "bar");
string fetched = dictionary["foo"]; // fetched = "bar"
You can have a "setter" on an indexer too:
dictionary["a"] = "b";
Your indexer is just returning the first AdminRecipient in the array with a matching type - or null if no match can be found.
(It's unfortunate that the code you've shown is also using a public field, by the way. It would be better as a property - and probably not an array, either. But that's a separate discussion.)
EDIT: Regarding the first line you highlighted:
public AdminRecipient[] AdminRecipientCollection = new AdminRecipient[0];
That will create an array with no elements, and assign a reference to the AdminRecipientCollection field. With no further changes, the foreach loop would not have anything to iterate over, and the indexer will always return null.
However, presumably something else - such as XML serialization - is assigning a different value to that field - populating it with more useful data.
The line you're asking about is an assignment of AdminRecipientCollection to an empty array. This assignment will occur at instantiation time. The deserialization of the XML will happen after the class is instantiated, if ever. The author's purpose here is presumably to ensure that AdminRecipientCollection will never be null, even in case the class is used before XML is deserialized into it.
By ensuring that AdminRecipientCollection is never null, the author can ignore the possibility that the foreach loop will throw a NullReferenceException. At least, that's probably the hope.
Unfortunately, because AdminRecipientCollection is public, the AdminRecipient property could still throw, as in the following case:
var demoClass = new DemoClass();
demoClass.AdminRecipientCollection = null;
var firstRecipient = demoClass[0];
To prevent this possibility, remove the initial assignment and check for null in the AdminRecipient property before beginning the enumeration.