If I've got a JSON array and I want to extract one field from each object, it's fairly simple:
Data:
{
"Values": [
{
"Name": "Bill",
"Age": "25",
"Address": "1234 Easy St."
},
{
"Name": "Bob",
"Age": "28",
"Address": "1600 Pennsylvania Ave."
},
{
"Name": "Joe",
"Age": "31",
"Address": "653 28th St NW"
}
]
}
Query:
data.SelectTokens("Values[*].Name")
This will give me an array of all the names. But what if I want more than one field? Is there any way to get an array of objects containing names and addresses?
The obvious way is to run SelectTokens twice and then Zip them, but will that work? Are the two result arrays guaranteed to preserve the ordering of the original source data? And is there a simpler way that can do it with just one query?
You can use the union operator ['Name','Address'] to select the values of multiple properties simultaneously. However, at some point you're going to need to generate new objects containing just the required properties, for instance by grouping them by parent object:
var query = data.SelectTokens("Values[*]['Name','Address']")
.Select(v => (JProperty)v.Parent) // Get parent JProperty (which encapsulates name as well as value)
.GroupBy(p => p.Parent) // Group by parent JObject
.Select(g => new JObject(g)); // Create a new object with the filtered properties
While this works and uses only one JSONPath query, it feels a little overly complex. I'd suggest just selecting for the objects, then using a nested query to get the required properties like so:
var query = data.SelectTokens("Values[*]")
.OfType<JObject>()
.Select(o => new JObject(o.Property("Name"), o.Property("Address")));
Or maybe
var query = data.SelectTokens("Values[*]")
.Select(o => new JObject(o.SelectTokens("['Name','Address']").Select(v => (JProperty)v.Parent)));
Demo fiddle here.
Related
I have a list of Students, each student can enter one or more addresses.
I have to find either any of the addresses overlapped in the list and then retrieve those overlapping objects from the list.
below is the example of the list of objects
[
{
"ID" : 1,
"Addresses": ["SRJ", "SJ"]
},
{
"ID" : 2,
"Addresses": ["SJ"}
},
{
"ID" : 3,
"Addresses": ["FRT", "FRI"}
},
{
"ID" : 4,
"Addresses": ["NR", "SJ"}
},
]
in the above list SJ is a repeated string in the three of the objects so, I have to return those three objects from the list with ID 1,2,4.
I have already done the above using loops but I am looking into the most efficient way of doing this task.
Assuming each list item in your question is mapped to a class like below:
public class AddressRecord
{
public int ID { get; set; }
public List<string> Addresses { get; set; }
}
You can then use a Linq expression like below to find duplicates and construct an object that tracks all duplicate addresses and IDs those addresses belong to:
var result = list.SelectMany(x => x.Addresses).GroupBy(x => x).Where(x => x.Count() > 1)
.Select(x => new { x.Key, IDs = list.Where(y => y.Addresses.Contains(x.Key)).Select(y => y.ID).ToList() })
.ToList()
First line in linq expression flattens the address list, runs a groupby expression on them and finds all addresses that exist more than once (across all "addresses" lists.)
Then the second line select each address with duplicates and IDs corresponding to that address. (Note that x in x.Key is the IGrouping object created by the groupby expression and x.Key is the address that the grouping was created on.)
The result object should look something like this when serialized:
[{"Key":"SJ","IDs":[1,2,4]}]
Runtime performance wise, a handmade for loop would most certainly beat this expression. However, from maintainability perspective, this linq expression is likely much easier to manage. (This does depend on comfort level of team with linq.)
I assume that you have a given address and want to check if that exists somewhere else:
string givenAddress = "SJ";
List<Student> overlappingAddressesStudents = students
.Where(s => s.Addresses.Contains(givenAddress))
.ToList();
This might not be more efficient that your loop approach, but maybe it's more readable.
I am trying to do a simple EF 6.0 query, but it seems it am getting nowhere and banging my head with it.
I am querying to take a single row as an object, but this return two dimentional array like below;
[
[],
[],
[{
Id: 1,
Name: Abc
}]
]
What I would like to return is the object.
{
Id: 1,
Name: Abc
}
and here is the query
var q = _operatorAssessmentContext.OperatorAssessmentQuestions
.Select(x => x.ConditionalQuestions.Where(c => c.Id == questionId)).ToList();
So the whole idea is to change that two dimentional list into an object.
First off, you say you want an object, but your variable, q, will be a list. I'm gonna assume you want an object, as that is what you stated.
Couple ways you can do this:
You can flatten the list, and then take one.
You can filter away empty lists in your list, and then take the first object of the first list in your list.
Personally, I would prefer the first, and the query going in to your variable, q, would look something like this:
var q = _operatorAssessmentContext
.OperatorAssessmentQuestions
.SelectMany(x => x.ConditionalQuestions.Where(c => c.Id == questionId))
.FirstOrDefault();
Note the only difference is me using .SelectMany(), which will take, as an input, a list of lists holding elements of some type, T, and return one list holding elements of the same type, T.
I have a list of names like so:
"John"
"Alan"
and a list of People that have all sorts of names
I can search through my people table for any names that match my list like this:
people = people.Where(x => names.Contains(x.Name));
However, this performs an exact match. How do I modify my query to be able to do a LIKE search on items in the names list?
Ie, I would like my original names list to find people in the people table that have names like
"John Smith"
"Bob Alanson"
etc.
You can use Any and Contains in which you already know how to use it.
var names = new List<string>() { "John", "Alan" };
var people = new List<string>() { "John Smith", "Bob Alanson" };
var result = people.Where(x => names.Any(y => x.Contains(y))).ToList();
The result is
John Smith
Bob Alanson
You can do that with Any and string.Contains
people = people.Where(x => names.Any(n => x.Name.Contains(n)));
You can use the following query:
people = people.Where(x => names.Like(x.Name, "%key%"));
Using Contains is close to Like (%pattern%). And some "Tom Johnson" will be selected to result collection. I prefer to use StartsWith (which implements Like(pattern%)) for filtering by name.
I have a simple document.
{
Name: "Foo",
Tags: [
{ Name: "Type", Value: "One" },
{ Name: "Category", Value: "A" },
{ Name: "Source", Value: "Example" },
]
}
I would like to make a LINQ query that can find these documents by matching multiple Tags.
i.e. Not a SQL query, unless there is no other option.
e.g.
var tagsToMatch = new List<Tag>()
{
new Tag("Type", "One"),
new Tag("Category", "A")
};
var query = client
.CreateDocumentQuery<T>(documentCollectionUri)
.Where(d => tagsToMatch.All(tagToMatch => d.Tags.Any(tag => tag == tagToMatch)));
Which gives me the error Method 'All' is not supported..
I have found examples where a single property on the child object is being matched: LINQ Query Issue with using Any on DocumentDB for child collection
var singleTagToMatch = tagsToMatch.First();
var query = client
.CreateDocumentQuery<T>(documentCollectionUri)
.SelectMany
(
d => d.Tags
.Where(t => t.Name == singleTagToMatch.Name && t.Value == singleTagToMatch.Value)
.Select(t => d)
);
But it's not obvious how that approach can be extended to support matching multiple child objects.
I found there's a function called ARRAY_CONTAINS which can be used: Azure DocumentDB ARRAY_CONTAINS on nested documents
But all the examples I came across are using SQL queries.
This thread indicates that LINQ support was "coming soon" in 2015, but it was never followed up so I assume it wasn't added.
I haven't come across any documentation for ARRAY_CONTAINS in LINQ, only in SQL.
I tried the following SQL query to see if it does what I want, and it didn't return any results:
SELECT Document
FROM Document
WHERE ARRAY_CONTAINS(Document.Tags, { Name: "Type", Value: "One" })
AND ARRAY_CONTAINS(Document.Tags, { Name: "Category", Value: "A" })
According to the comments on this answer, ARRAY_CONTAINS only works on arrays of primitives, not objects. SO it appears not to be suited for what I want to achieve.
It seems the comments on that answer are wrong, and I had syntax errors in my query. I needed to add double quotes around the property names.
Running this query did return the results I wanted:
SELECT Document
FROM Document
WHERE ARRAY_CONTAINS(Document.Tags, { "Name": "Type", "Value": "One" })
AND ARRAY_CONTAINS(Document.Tags, { "Name": "Category", "Value": "A" })
So ARRAY_CONTAINS does appear to achieve what I want, so I'm looking for how to use it via the LINQ syntax.
Using .Contains in the LINQ query will generate SQL that uses ARRAY_CONTAINS.
So:
var tagsToMatch = new List<Tag>()
{
new Tag("Type", "One"),
new Tag("Category", "A")
};
var singleTagToMatch = tagsToMatch.First();
var query = client
.CreateDocumentQuery<T>(documentCollectionUri)
.Where(d => d.Tags.Contains(singleTagToMatch));
Will become:
SELECT * FROM root WHERE ARRAY_CONTAINS(root["Tags"], {"Name":"Type","Value":"One"})
You can chain .Where calls to create a chain of AND predicates.
So:
var query = client.CreateDocumentQuery<T>(documentCollectionUri)
foreach (var tagToMatch in tagsToMatch)
{
query = query.Where(s => s.Tags.Contains(tagToMatch));
}
Will become:
SELECT * FROM root WHERE ARRAY_CONTAINS(root["Tags"], {"Name":"Type","Value":"One"}) AND ARRAY_CONTAINS(root["Tags"], {"Name":"Category","Value":"A"})
If you need to chain the predicates using OR then you'll need some expression predicate builder library.
Background / Goal
I have a query in ElasticSearch where I'm using filters on a several fields (relatively small data set and we know exactly what values should be in those fields at the time we query). The idea is that we'll perform a full-text query but only after we've filtered on some selections as made by the user.
I'm putting ElasticSearch behind a WebAPI controller and figured it made sense to use NEST to accomplish the query.
The query, in plain English
We have filters for several fields. Each inner filter is an or filter, but they're together as an AND.
In SQL, the pseudo-code equivalent would be select * from table where foo in (1,2,3) AND bar in (4,5,6).
Questions
Can I simplify the way I'm thinking about this query, based on what you see below? Am I overlooking some basic approach? This seems heavy but I'm new to ES.
How would I properly represent the query below in NEST syntax?
Is NEST the best choice for this? Should I be using the ElasticSearch library instead and going lower level?
The Query Text
{
"query": {
"filtered": {
"filter": {
"bool": {
"must": [
{
"or": [
{ "term": { "foo": "something" } },
{ "term": { "foo": "somethingElse" } }
]
},
{
"or": [
{ "term": { "bar": "something" } },
{ "term": { "bar": "somethingElse" } }
]
}
]
}
}
}
},
"size": 100
}
This kind of task is quite simple and popular in ES.
You can represent it in NEST like following:
var rs = es.Search<dynamic>(s => s
.Index("your_index").Type("your_type")
.From(0).Size(100)
.Query(q => q
.Filtered(fq => fq
.Filter(ff => ff
.Bool(b => b
.Must(
m1 => m1.Terms("foo", new string[] { "something", "somethingElse" }),
m2 => m2.Terms("bar", new string[] { "something", "somethingElse" })
)
)
)
.Query(qq => qq
.MatchAll()
)
)
)
);
Some notes:
I use filtered query to filter what I need first, then search stuffs later. In this case the it will filter for foo in ("something", "somethingElse") AND bar in ("something", "somethingElse"), then query all filtered results (match_all). You can change match_all to what you need. filtered query it's for best performance as ES will only need to evaluate scores of documents in query part (after filtered), not all documents.
I use terms filter, which more simple and better performance than or. Default mode of terms is OR all input terms, you can refer more in document about available modes (AND, OR, PLAIN, ...).
Nest is best choice for .NET in my opinion as it designed for simple & easy to use purposes. I only used lower API if I want to use new features that Nest does not support at that time, or if Nest have bugs in functions I use.
You can refer here for a brief NEST tutorial: http://nest.azurewebsites.net/nest/writing-queries.html
Updated: Building bool filters dynamic:
var filters = new List<Nest.FilterContainer>();
filters.Add(Nest.Filter<dynamic>.Terms("foo", new string[] { "something", "somethingElse" }));
// ... more filter
then replace .Bool(b => b.Must(...)) with .Bool(b => b.Must(filters.ToArray()))
Hope it help