I want to join two json files using a common key and get all the records from the right file and matching data from the left.
If it was SQL.
SELECT json1.CategoryDescription, json2.CategoryID, json2.TechName, json2.SpawnID
FROM json1
RIGHT JOIN json2
ON json1.CategoryID = json2.CategoryID
WHERE GameVersion = "A" OR GameVersoion = "2" AND CategoryID = "metals"
I need to get all the json2 records and the json1.CategoryDescription for each of them. But at the moment it just lists all the records from json1 then all the records from json2.
Here is my current attempt:
using System;
using System.IO;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
namespace ConsoleApp1
{
public class Program
{
public static void Main()
{
// Filter variables
var gameVer = "2";
var cat = "metals";
// Load the categories.json
JObject catObj = JObject.Load(new JsonTextReader(File.OpenText("D:/Code/Tests/categories.json")));
// Load the techtype.json
JObject ttObj = JObject.Load(new JsonTextReader(File.OpenText("D:/Code/Tests/techtypes.json")));
// Read techtype.json into an array
var mergeSettings = new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Union
};
catObj.Merge(ttObj, mergeSettings);
// Does not work,
/*
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
at ConsoleApp1.Program.Main() in D:\Code\Tests\ReadTechTypes\ReadTechTypes\Program.cs:line 30
*/
// (catObj.SelectToken("Categoris") as JArray).Merge(ttObj.SelectToken("TechType"), mergeSettings);
// Does not work, same error
//var mergedArray = catObj.SelectToken("Categoris") as JArray;
//string json = mergedArray.ToString();
Console.WriteLine(catObj);
}
}
}
The left json
{
"Categories":[
{
"CategoryID":"baseupgrades",
"CategoryDescription":"Base Upgrades",
"IncludeCategory":true,
"GameVersion":"A"
},
{
"CategoryID":"batteries",
"CategoryDescription":"Batteries",
"IncludeCategory":true,
"GameVersion":"A"
},
{
"CategoryID":"blueprint",
"CategoryDescription":"Blueprint",
"IncludeCategory":false,
"GameVersion":"A"
}
// Other category values omitted
]
}
The right json
{
"Items":[
{
"CategoryID":"crystalline",
"TechName":"Quartz",
"SpawnID":"quartz",
"TechID":1,
"GameVersion":"A"
},
{
"CategoryID":"metals",
"TechName":"Metal Salvage",
"SpawnID":"scrapmetal",
"TechID":2,
"GameVersion":"A"
},
{
"CategoryID":"outcrop",
"TechName":"Limestone Outcrop",
"SpawnID":"limestonechunk",
"TechID":4,
"GameVersion":"A"
}
// Other items omitted
]
}
Any ideas?
You can try this
categoriesRoot = JsonConvert.DeserializeObject<CategoriesRoot>(categoriesJson);
itemsRoot = JsonConvert.DeserializeObject<ItemsRoot>(itemsJson);
var items = from cr in categoriesRoot.Categories
join ir in itemsRoot.Items on cr.CategoryID equals ir.CategoryID into irj
from ir in irj.DefaultIfEmpty()
where ( (cr.GameVersion == "A") || (cr.GameVersion == "2" && cr.CategoryID == "metals"))
select new {
cr.CategoryDescription,
ir.CategoryID,
ir.TechName,
ir.SpawnID
};
var newItemsJson=JsonConvert.SerializeObject(items);
after creating these classes
public class Item
{
public string CategoryID { get; set; }
public string TechName { get; set; }
public string SpawnID { get; set; }
public int TechID { get; set; }
public string GameVersion { get; set; }
}
public class ItemsRoot
{
public List<Item> Items { get; set; }
}
public class Category
{
public string CategoryID { get; set; }
public string CategoryDescription { get; set; }
public bool IncludeCategory { get; set; }
public string GameVersion { get; set; }
}
public class CategoriesRoot
{
public List<Category> Categories { get; set; }
}
output will be like this
[
{"CategoryDescription":"Base Upgrades","CategoryID":"crystalline","TechName":"Quartz","SpawnID":"quartz"},
{"CategoryDescription":"Batteries","CategoryID":"metals","TechName":"Metal Salvage","SpawnID":"scrapmetal"}
]
And by the way you have a bug in your SQL query
WHERE GameVersion = "A" OR GameVersoion = "2" AND CategoryID = "metals"
this is an ambiguous code, since there are GameVersion and CategoryID in both queries.
The following should work:
// Filter variables
var gameVersions = new HashSet<string> { "A", "2" };
var categoryIDs = new HashSet<string> { "metals" };
// Left outer join on ttObj. Select all Items[*] array items
var query = from i in ttObj.SelectTokens("Items[*]").OfType<JObject>()
// Filter on the game version and category ID
let categoryId = (string)i["CategoryID"]
let gameVersion = (string)i["GameVersion"]
where categoryIDs.Contains(categoryId) && gameVersions.Contains(gameVersion)
// Join with "Categories[*]" on category ID
join c in catObj.SelectTokens("Categories[*]") on categoryId equals (string)c["CategoryID"] into joined
// DefaultIfEmpty makes this a left join
from cat in joined.DefaultIfEmpty()
// Select all records of i and add the CategoryDescription from cat.
select new JObject(i.Properties()) { new JProperty("CategoryDescription", cat?["CategoryDescription"]) };
var results = query.ToList(); // Materialize the query into a list of results.
Which results in:
[
{
"CategoryID": "metals",
"TechName": "Metal Salvage",
"SpawnID": "scrapmetal",
"TechID": 2,
"GameVersion": "A",
"CategoryDescription": null
}
]
Notes:
I changed the query from a right join to a left join because it made the filtering look a little more natural. See LINQ Left Join And Right Join if you would prefer the right join syntax.
The final select statements creates a new JObject with all the records from the JObject i item object then adds the CategoryDescription from the cat category object. It does not modify the existing object i.
JContainer.Merge() isn't going to help you here because it doesn't have any ability to merge based on some primary key.
ttObj.SelectTokens("Items[*]") uses the JSONPath wildcard operator [*] to select all items in the "Items" array.
As there is no category with "CategoryID":"metals", cat is null in the ultimate select statement.
Demo fiddle here.
The problem is that you are merging the "Category" list with the "Items" list, and "Items" is not present on catObj.
[I suggest to you to convert the items in class (with visual studio you can do a "Special paste" as JSON class).]
You have to iterate over the items of the first list and merge with the corresponding element in the second list, member with member, not list with list.
Related
I have the following problem: the query with linq works ok, but I need to add the coordinates within an array for each record that is repeated
Model
public class AppPoisModel
{
public int idPoiType { get; set; }
public string sector { get; set; }
public double latPoint { get; set; }
public double lngPoint { get; set; }
}
Query
var result = (from c in db.coord
select new AppPoisModel
{
idPoiType = c.id,
sector = c.sector,
latPoint = c.latitude ?? 0,
lngPoint = c.longitude ?? 0
}).ToList();
Result
[
{
"$id": "1",
"sector" : "A",
"latPoint": 0,
"lngPoint": 0
},
{
"$id": "2",
"sector" : "A",
"latPoint": 0,
"lngPoint": 0
}
]
Desired result
[
{
"$id": "1",
"sector" : "A",
"coords": [{latPoint, lngPoint}, {latPoint, lngPoint}]
}
]
thank you very much for your contributions
looks like you need a group by...
Also you might create a class for Coords, and make AppPoisModel or a new result class, with a coords field typed as a collection of coords
check it out: Group by in LINQ
similar solution https://stackoverflow.com/a/47580961
The initial query you suggested has no clue that there's a common relationship with items in the same sector.
To accomplish this, you'll want to use the Enumerable.GroupBy() method to group those items together with the basis of having the same id and sector together.
If both of those will always be correlated, you can just GroupBy() one of them to make the comparison simpler, but the results will also reflect that.
var result = (from c in db.coord
select new AppPoisModel
{
idPoiType = c.id,
sector = c.sector,
latPoint = c.latitude ?? 0,
lngPoint = c.longitude ?? 0
}).GroupBy(x => new { id = x.idPoiType, sector = x.sector });
In your case, possibly with both id and sector. This will be your key when you want to loop over the results. So that you can morph the results into the data type you want.
i need to print all rows from one table.
avoid placing: s.Adress+s.EmployeeID, etc
var query = from x
in bd.Employees
where x.City == "London" && x.TitleOfCourtesy == "Mr."
select x;
foreach(var s in query)
{
Console.WriteLine(s.Address+"---"+s.EmployeeID);
}
Console.WriteLine takes params so you can do this:
Console.WriteLine("{0}---{1}", s.Address, s.EmployeeID.ToString());
Or you can use C# 6.0 string interpolation (Note the dollar sign):
Console.WriteLine($"{s.Address}---{s.EmployeeID}");
EDIT
Since you mentioned in the comments:
I want to print all rows from each column from table Employees (Northwind db), without writing each of the columns names
You can do this, imagine you have a class:
public class One
{
public int Id { get; set; }
public string Name { get; set; }
}
You can:
// using System.Web.Script.Serialization;
var ser = new JavaScriptSerializer();
var one = ser.Serialize(new One() { Id = 1, Name = "George" });
Console.WriteLine(one);
Trying to figure out how to query an IEnumerable<T> using LINQ. The following simple example without IEnumerable works fine:
class Category
{
public string Title { get; set; }
public NameValue SubCategory { get; set; }
}
class NameValue
{
public string Name { get; set; }
public string Value { get; set; }
}
private static void testLinq()
{
Category[] categories = {
new Category { Title ="Abc", SubCategory = new NameValue { Name = "A", Value = "5"} },
new Category { Title ="Xyz", SubCategory = new NameValue { Name = "B", Value = "10" } }
};
IEnumerable<Category> q = categories.OrderBy(c => c.Title).ThenBy(c => c.SubCategory.Name);
foreach (Category c in q)
{
Console.WriteLine("{0} - {1}", c.Title, c.SubCategory.Name);
}
}
When I change the signature to have an IENumerable<NameValue> instead then I cannot access c.SubCategory.Name:
class Category
{
public string Title { get; set; }
public IEnumerable<NameValue> SubCategory { get; set; }
}
// For example, below does not compile:
IEnumerable<Category> q = categories.OrderBy(c => c.Title).ThenBy(c => c.SubCategory.Name);
// Also, this initialization of course won't work either
Category[] categories = {
new Category { Title ="Abc", SubCategory = new NameValue { Name = "A", Value = "5"} },
new Category { Title ="Xyz", SubCategory = new NameValue { Name = "B", Value = "10" } }
};
The error is:
IEnumerable' does not contain a definition for 'Name' and no extension method 'Name' accepting a first argument of type 'IEnumerable' could be found (are you missing a using directive or an assembly reference?)
Do I need to do a cast of some sort?
Update:
Output should be something like:
Abc (category)
A (sub)
B (sub)
C (...)
Xyz
B
K
M
Xyz2
A
Q
Z
In SQL I would do like something like this:
SELECT c.Title, s.Name, s.Value FROM Categories c
INNER JOIN SubCategory s ON
c.CategoryID = s.CategoryID
ORDER BY c.Title, s.Name -- sorting first on Category.Title, then on SubCategory.Name
Your SubCategory will be a collection so you cannot do it using ThenBy. You need to order the Category(s) and then order their SubCategory(s) like this. Note I added two SubCategory(s) to the first Category but their order is not correct. After we order them, then they will be correct:
Category[] categories = {
new Category { Title ="Abc", SubCategory = new List<NameValue>
{ new NameValue { Name = "B", Value = "5"},
new NameValue { Name = "A", Value = "5"} } },
new Category { Title ="Xyz", SubCategory = new List<NameValue>
{ new NameValue { Name = "A", Value = "10" } } }};
// First order by categories
var cats = categories.OrderBy(c => c.Title)
// Then create a new category and order by sub categories
.Select(x => new Category { Title = x.Title,
SubCategory = x.SubCategory.OrderBy(y => y.Name) });
If you can get away with only sorting the children when you need to use them, sorting by the parent and then sorting the children upon use like this would be fairly efficient:
public void DisplayA(A value)
{
Console.WriteLine(value.Name);
foreach (var child in value.Children.OrderBy(c => c.Name))
{
Console.WriteLine(string.Format("- {0}", child.Name));
}
}
Or if you want to avoid that, you could add a sorted property to the class. Since it's Linq, it will only be evaluated when you iterate through the list.
public class A
{
public string Name { get; set; }
public IEnumerable<B> Children { get; set; }
public IEnumerable<B> SortedChildren { get { return Children.OrderBy(ca => ca.Name); } }
}
public class B
{
public string Name { get; set; }
}
If they don't work for you, you could try these, but they won't be so efficient since you're creating new objects.
// This will flatten it into a single object, sorted by one field and the the other. Since this is Linq, it will create these new flattened objects each time you iterate through the IEnumerable.
public IEnumerable<FlattenedA> GetSortedFlattened(IEnumerable<A> collection)
{
var flattened = collection.SelectMany(a => a.Children.Select(ca => new FlattenedA() { Name = a.Name, SubName = ca.Name }));
var sorted = flattened.OrderBy(f => f.Name).ThenBy(f => f.SubName);
return sorted;
}
// This will return objects of A, where the child enumerable has been replaced with an OrderBy. Again this will return new objects each time you iterate through. Only when you iterate through the children will they be sorted.
public IEnumerable<A> GetSortedNonFlattened(IEnumerable<A> collection)
{
var withSortedChildren = collection.Select(a => new A() { Name = a.Name, Children = a.Children.OrderBy(ca => ca.Name) });
var sorted = withSortedChildren.OrderBy(a => a.Name);
return sorted;
}
public class FlattenedA
{
public string Name { get;set }
public string SubName { get; set; }
}
public class A
{
public string Name { get; set; }
public IEnumerable<B> Children { get; set; }
}
when you are setting it as IEnumerable you can't do this
SubCategory = new NameValue { Name = "A", Value = "5"}
you should use some implementation of IEnumerable,
like List<>
so it should be something like this
SubCategory = new List<NameValue>{new NameValue { Name = "A", Value = "5"}, addmore here};
and for your order linq, i would do this,
var OrderedCategories = categories.select(g =>
new Category{ Name = g.Name, subcategories = g.subcategories.orderby(h => h.Name) });
That's because your SubCategory now is no longer a simple single instance of NameValue, but rather an enumeration of those. So now you need to specify how to .ThenBy over a collection of .Names.
I am trying to obtain a child element of a parent element. I have a basic database as so:
public class list
{
public int id { get; set; }
public List<Users> UsersList{ get; set; }
public class Users
{
[Key]
public int Users_id{ get; set; }
public string UserId { get; set; }
}
}
If I was wanting to obtain all of the of the elements in Users that had the specific UserId how would I do that? I am trying to refrain from using a nested for loop and iterating through all my entries of List and Users in the database. I was looking up the LookUp(), but am a bit confused on how to apply it in this case. Any help would be great!
Since you're a bit confused, I'll provide more detail than my original answer. Let's take your code and create a very basic and crude sample program:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace SOSample
{
public class list
{
public int id { get; set; }
public List<Users> UsersList { get; set; }
public class Users
{
[Key]
public int Users_id { get; set; }
public string UserId { get; set; }
}
}
class Program
{
static void Main(string[] args)
{
// Instantiate and initialize with sample data.
var sampleList = new list()
{
id = 12345,
UsersList = new List<list.Users>()
{
new list.Users() { Users_id = 1, UserId = "0042" },
new list.Users() { Users_id = 2, UserId = "0019" },
new list.Users() { Users_id = 3, UserId = "0036" },
new list.Users() { Users_id = 4, UserId = "0214" },
new list.Users() { Users_id = 5, UserId = "0042" },
new list.Users() { Users_id = 6, UserId = "0042" },
new list.Users() { Users_id = 7, UserId = "0019" }
}
};
// Linq search.
var someId = "0042";
var linqQuery = sampleList.UsersList.Where(user => user.UserId == someId);
Console.WriteLine("Linq query results:");
foreach (var r in linqQuery)
{
Console.WriteLine($"Users_id: {r.Users_id}, UserId: {r.UserId}");
}
// Lookup search (using same someId as for Linq).
var lookup = sampleList.UsersList.ToLookup(user => user.UserId);
var lookupQuery = lookup[someId];
Console.WriteLine("\nLookup query results:");
foreach (var r in lookupQuery)
{
Console.WriteLine($"Users_id: {r.Users_id}, UserId: {r.UserId}");
}
}
}
}
Output:
Linq query results:
Users_id: 1, UserId: 0042
Users_id: 5, UserId: 0042
Users_id: 6, UserId: 0042
Lookup query results:
Users_id: 1, UserId: 0042
Users_id: 5, UserId: 0042
Users_id: 6, UserId: 0042
Hope that clarifies things. The major issue I see with your question and comments is that it's possible that you're mistaking nested classes for properties. When you instantiate an outer class, the inner class does not get instantiated and it's not some sort of property of an outer class.
Old answer (provides individual details):
I like using Linq. So, assuming sampleList is of type list:
var query = sampleList.UsersList.Where(user => user.UserId == someId);
That's going to give you IEnumerable<list.Users>. You can always use ToList(), ToArray(), ToDictionary() to get the desired collection type:
var results = sampleList.UsersList.Where(user => user.UserId == someId).ToArray();
As far as Lookup, I've seen a few ways it being used, but the most familiar way for me is this:
var lookup = sampleList.UsersList.ToLookup(user => user.UserId);
var query = lookup[someId];
Once again, that'll give you IEnumerable<list.Users>. Alternatively, you can get the collection type of your choice from that query:
var results = lookup[someId].ToArray();
Basically, you're specifying what the key will represent in that lookup (it's the UserId in this case) and then when the time comes, you search by a key.
I am trying to get a list filtered based on the matches of one of the properties with a property of another list.
In below example, only the items which have common 'name' between both lists should be filtered in 1st list. Can some one tell me the most concise way of doing it?
class TCapability
{
public string Name { get; set; }
public int Id { get; set; }
}
class PCapability
{
public string Name { get; set; }
public int Code { get; set; }
}
Input:
var capability = new List<TCapability>()
{
new TCapability() {Name="a", Id=1},
new TCapability() {Name="b", Id=2},
new TCapability() {Name="c", Id=3}
};
var type2Capability = new List<PCapability>()
{
new PCapability() {Name="a", Code=100},
new PCapability() {Name="b", Code=200},
new PCapability() {Name="d", Code=300}
};
Expected Output:
capability =
{
{ Name="a", Id=1 },
{ Name="b", Id=2 }
}
var result = capability.Where(c => type2Capability.Any(c2 => c.Name == c2.Name));
you can try use join clause like this
capability = (from a in capability
join b in type2Capability on a.Name equals b.Name
select a).ToList();
UPDATE on comment if type2Capability can have duplicate names
capability = (from a in capability
join b in type2Capability on a.Name equals b.Name into f
where f.Any()
select a).ToList();
If the lists can get long then a HashSet can speed things up.
var set = new HashSet<string>(type2Capability.Select(t => t.Name));
var res = capability.Where(c => set.Contains(c.Name));