How do I get a list of all properties in a JSON document? - recursion

I have a dynamic JSON document but for the sake of this question, imagine it resembles the following:
{
"name": "Jungle Gym",
"age": 25,
"favorite_color": "#ffa500",
"gender": "male",
"location": {
"city": "Seattle",
"state": "WA",
"citystate": "Seattle, WA"
},
"pets": [
{
"type": "dog",
"name": "Foo",
"food": [
"chicken",
"beef",
"fish"
]
},
{
"type": "cat",
"name": "Bar"
}
]
}
How do I get a list of all property names in the document? I can get the top level property names ("name", "age", "favorite_color", "gender", "location", "pets") but I need to get all property names down to "location.state" or "pets.food" and, if the properties / objects exist, even deeper.
I started using the following:
var model = JsonConvert.DeserializeObject<JObject>(json);
foreach (JProperty property in model.Properties())
{
if (property.Value.Type == JTokenType.Object)
{
foreach (var tmp in property.Children())
{
Console.WriteLine(tmp.SelectToken().);
}
}
}
but cannot seem to navigate into the objects beyond the top level to get the next level of properties (location properties, e.g.). Ideally, I'd like the list of derived property names to include the full path to the property, e.g., location.city, location.state, etc., instead of just the property name, e.g. city, state.
How can I get a list of all properties in a JSON document with potentially various levels of nesting?

In order to get all the keys in Json string (all levels), you could use Descendants and Path properties to fetch the details.
var data = JObject.Parse(jsonString);
var result = data.Descendants()
.OfType<JProperty>()
.Select(f=>Regex.Replace(f.Path,#"\[[0-9]\]",string.Empty))
.Distinct();
Please note the Regex is used to replace all the indices that might result due to pets list.
Output,

Related

Azure Cosmos SQL - working with nested arrays and using LIKE keyword

For some time when I had to find particular element of array by particular value I've been using ARRAY_CONTAINS function. Now I have documents with nested arrays where I have to search not but particular value, but using regex.
As an example of document let me use one from official documentation:
{
"id": "AndersenFamily",
"lastName": "Andersen",
"parents": [
{ "firstName": "Thomas" },
{ "firstName": "Mary Kay"}
],
"children": [
{
"firstName": "Henriette Thaulow",
"gender": "female",
"grade": 5,
"pets": [{ "givenName": "Fluffy" }]
}
],
"address": { "state": "WA", "county": "King", "city": "Seattle" },
"creationDate": 1431620472,
"isRegistered": true
}
What I need is to select and fully get all documents where at least one of children has at least one pets element where givenName contains "fluf".
What SQL query do I build to achieve it?
Here's a query that uses JOINs to flatten out the inner pets arrays and apply a filter, then return the entire matching family items:
SELECT VALUE f
FROM Families f
JOIN c IN f.children
JOIN p IN c.pets
WHERE p.givenName LIKE "%Fluf%"
The complexity of figuring out such queries is one reason why I think it's worth considering modeling data to be as flat as possible, including normalizing out to have separate pets items for example, which can be queried with direct property filters without dealing with nesting. Combining everything into a large Family object as the examples do isn't necessarily a good idea in practice depending on your goals.

modify nested object but maintain the full object after

I need to modify an element --an array-- (e.g.: "group-xyz") within a nested object in a JSON tree using JQ but once that's done then I need the entire object back with the modified data.
The goal is to update a JSON tree and save it in full.
e.g.: add array element, empty array, etc.
{
"group-abc": {"users": ["tina.turner"]},
"group-def": {"users": ["someone.else"]},
"group-xyz": {"users": ["that.thing"]
}
Then I am interested in returning an object like this:
{
"group-abc": {"users": ["tina.turner"]},
"group-def": {"users": []},
"group-xyz": {"users": ["that.thing","well.done"]
}
I have changed my requirements to fit a more complex form. To add a user to any of these groups' users this is what I did:
jq '. |= map( if ( .group=="abc") then .users+=["final.answer",] else . end)' source.json
which produced a result
[
{
"group": "abc",
"users": [
"user1",
"user2",
"final.answer"
]
},
{
"group": "def",
"users": [
"user4",
"user5"
]
}
]

How to represent responses with heterogenous arrays in pact-jvm

I'm having trouble figuring out how to represent arrays with structurally different objects in an array in pact contracts.
From pact-spec-v3
It would also be required to define whether the matchers should be
combined with logical AND (all matchers must match) or OR (at least
one matcher must match). AND should be the default, but there are
cases where an OR makes sense
Can this be leveraged to 'OR' the two different types of objects ?
This is the response that I'm trying to model via the pact-jvm DSL, the suggestions array contains two objects of different types, '1' and '3', having different schemas -
{
"suggestions": [
{
"display_name": "Potato",
"type": 1,
"keyword": "Potato",
"category_l1": {
"icon_image_url": "XXXXX",
"id": 1489,
"name": "Potato"
}
},
{
"type": 3,
"suggestion": {
"display_name": "New Potato (Aloo)",
"name": "New Potato"
}
}
]
}
Code that I have so far:
private DslPart getBody() {
return new PactDslJsonBody()
.eachLike("suggestions", 1)
.stringType("display_name")
.integerType("type")
.stringType("keyword")
.object("category_l1")
.stringType("icon_image_url")
.stringType("name")
.integerType("id")
.closeObject()
.closeObject()
.closeArray();
}
This is not currently easy to model with Pact, it works assuming each item is similar to a provided example. For a discussion, see https://github.com/pact-foundation/pact-specification/issues/38

How to filter a non-array in JsonPath

Using the following JSON (from http://jsonpath.com):
{
"firstName": "John",
"lastName" : "doe",
"age" : 26,
"address" : {
"streetAddress": "naist street",
"city" : "Nara",
"postalCode" : "630-0192"
},
"phoneNumbers": [
{
"type" : "iPhone",
"number": "0123-4567-8888"
},
{
"type" : "home",
"number": "0123-4567-8910"
}
]
}
I would like to get the root object only if firstName is John.
I have tried these inputs and many other similar ones:
$.[?($.firstName == 'John')]
$.[?($.'firstName' == 'John')]
$.[?(#.firstName == 'John')]
$[?($.firstName == "John")]
It seems as though filtering is only intended for arrays so this is an unsupported function. Does someone know a way to do this in Json.NET, or confirm that it's not possible and maybe point me to a library which supports the above?
I'm using F# but that's not important because F# is compatible with C#, .NET and NuGet packages.
JSON path is intended to locate data in a JSON object and not to perform some processing or testing on that data. The filter notation is used to identify an item in an array with the purpose of returning that data or some part of it. Having objects in an array means that there may be many properties with the same name that have to be filtered by some other means in order to select a subset of them.
Using filter notation on an object property is not the same thing. There can only be one property in an object with a particular name so stating that name is sufficient to identify it uniquely. You can easily achieve the effect you require by getting $.firstName and then testing separately for the value "John"

How to create GTM data layer variable with complex array

In Google Tag Manager a pre-defined variable type of "Data Layer Variable" exists with an input for the variable name. In a standard single level of key/value pairs this is easy.
var dataLayer = [{"mykey":"myvalue"}];
Given that data layer you'd just use mykey as your variable to input into GTM. However, if you use the CEDDL spec (http://www.w3.org/2013/12/ceddl-201312.pdf) structure you end up with a deeply nested array:
dataLayer = [
{
"product": [
{
"category": {
"primaryCategory": "Auto Loans"
},
"productInfo": {
"productID": "1",
"productName": "PurchaseLoan",
"description": "Auto finance loan"
},
"security": [
"Analytics",
"Personalization",
"Recommendations"
]
}
]
}
]
So the real question is: how do I access the value of "productName" in the above example?
In standard Javascript you might access it like so:
dataLayer[1].product[0].productInfo.productName
or
dataLayer.1.product.1.productInfo.productName
... but neither of these options work (with or without dataLayer.1 as the first node).
This is the UI to enter the variable name:
When you define your DataLayer variable in GTM, you don't need to specify "dataLayer" in the variable name, ie. it should just be:
product.0.productInfo.productName

Resources