I am making a web service which will give a response in JSON format. I have fetched data from sql server and stored it in a Datatable. This is how the dt looks:-
id Caption pid
F182 GLOBAL REPORTS NULL
F184 software NULL
F1227 LYB P&L Reports F184
F1245 LYB Training F184
F1239 test3 F182
F1249 Paavan_Test_Reports F184
Items in caption column which have pid as Null are the parents and they have children which have the same pid as their respective parent's id.
Eg: GLOBAL REPORTS has 1 child i.e test3 and software has 3 child.
I want the JSON response into the following format
[{
id='F182',
caption='GLOBAL REPORTS',
pid=null;
items:[{
id='F1239',
caption='test3',
pid='F182'}]
},
{
id='F184',
caption='software',
pid='NULL',
items:[{
id='F1227',
caption='LYB P&L Reports',
pid='F184'
},
{
id='F1245',
caption='LYB Training',
pid='F184'
},
{
id='F1249',
caption='Paavan_Test_Reports',
pid='F184'
}
}]
i have made a class folder
class folder
{
string id{get;set;}
string pid{get;set;}
string caption{get;set;}
}
How can i get the array of objects items which will contain all the available child of a particular parent? I am new to Json objects and responses,
I have tried using the following method:
var obj = dt.AsEnumerable()
.GroupBy(r => r["pid"])
.ToDictionary(g => g.Key.ToString(),
g => g.Select(r => new {
item = r["caption"].ToString(),
}).ToArray());
var json = JsonConvert.SerializeObject(obj);
but this is giving me a simple json response without any hierarchy.
You need to build object hierarchy before serialization. To do so define a new property in your folder class:
public IEnumerable<folder> items { get; set; }
Now you can recursively search childs for each root folder:
public static IEnumerable<folder> BuildTree(folder current, folder[] allItems)
{
var childs = allItems.Where(c => c.pid == current.id).ToArray();
foreach (var child in childs)
child.items = BuildTree(child, allItems);
current.items = childs;
return childs;
}
Usage:
var input = new[] // dt.AsEnumerable() in your case
{
new folder {id = "F182", caption = "GLOBAL REPORTS", pid = null },
new folder {id = "F184", caption = "software", pid = null },
new folder {id = "F1227", caption = "LYB P&L Reports", pid = "F184" },
new folder {id = "F1245", caption = "LYB Training", pid = "F184" },
new folder {id = "F1239", caption = "test3", pid = "F182" },
new folder {id = "F1249", caption = "Paavan_Test_Reports", pid = "F184" },
};
var roots = input.Where(i => i.pid == null);
foreach (var root in roots)
BuildTree(root, input);
var json = JsonConvert.SerializeObject(roots, Formatting.Indented);
Also, if you want to hide empty items you can define method ShouldSerialize in folder class:
public bool ShouldSerializeitems()
{
return items.Any();
}
Demo is here
I think what you want is to have a list of folders inside the folder, like the following:
class folder
{
string id{get;set;}
string pid{get;set;}
string caption{get;set;}
List<folder> items {get;set;}
}
When you use JSONConvert in this case, you can run into having circular dependencies, if a parent exists in the child's collection. You can use a SerializerSettings object to ignore these cases, and for children in the items list, you can set their collection to null, so it won't serialized.
However, the desired output you specify also lists an array of folders. So in the end, you'll need to serialize a collection(List or array for example) of the folder class. Something like.
With regards to how you would load in the data, I imagine you've already loaded the folders with id, pid and caption, but the items property is empty.
In order to establish the parent/child relation, you could so something like
var folders = dt.AsEnumerable().ToList();
foreach(var folder in folders) {
folder.items = folders.Where(f => f.pid.Equals(folder.id)).ToList();
}
var parentFolders = folders.Where(f => f.pid is null);
var json = JsonConvert.SerializeObject(parentFolders, new SerializerSettings () {
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
Related
I have a default list of attributes and in incoming list of attributes. I need to remove any items from the incoming list that do now match the Name in the default list. I have tried many different LINQ queries, but have not been able to accomplish this task.
Default List:
Attributes[] defaultAttributes =
{
new ProfileAttributes() { Name = "FirstName", Include = false, Required = false },
new ProfileAttributes() { Name = "MiddleName", Include = false, Required = false },
new ProfileAttributes() { Name = "HomeCountry", Include = false, Required = false },
...
I want to merge the two lists and remove any items where the Name of the incoming list does not match the default list.
For example in the following remove Favorite color because it is an invalid name and preserve the required values.
Attributes[] incomingAttributes =
{
new ProfileAttributes() { Name = "FavoriteColor", Required = true },
new ProfileAttributes() { Name = "MiddleName", Required = false},
new ProfileAttributes() { Name = "HomeCountry", Required = true },
Most incoming lists will not have "Include" So I need to add that and set it to true if it is in the incoming list, otherwise false. I have done that with the following, but interested if there is a way to combine this with the merge.
Revised, I used the following solution:
I used lists instead of array lists. I found this easier to loop through and bind to checkboxes on the form
Attributes[] defaultAttributes
to
List<ProfileAttributes> defaultAttributes = new List<ProfileAttributes>()
Inside the loop for my form:
<input type="checkbox"for="myModel.ProfileAttributes[i].Include"
I created an empty list:
List<ProfileAttributes> validListAttributes = new();
Then I created a loop. If the name is valid add it to the empty list and add the Include attribute:
foreach (var a in myModel.ProfileAttributes) //incomingAttributes
{
if (defaultAttributes.Any(d => d.Name == a.Name))
{
a.Include = true;
validListAttributes.Add(a);
}
}
Then another loop to add missing attributes because all attributes must be display on the form:
foreach (var d in defaultAttributes)
{
if (!validListAttributes.Any(v => v.Name == d.Name))
{
validListAttributes.Add(d);
}
}
Then update the model with the valid list containing all attributes:
myModel.ProfileAttributes = validListAttributes.ToList();
This will be a lot easier with a generic IEqualityComparer whose job is to compare the Name property of the instances involved in the process.
So let's define an IEqualityComparer for the Attributes class
public class ProfileAttributesComparer : IEqualityComparer<ProfileAttributes>
{
public bool Equals(ProfileAttributes obj1, ProfileAttributes obj2)
{
if(obj1 == null && obj2 == null)
return true;
if(obj1 == null || obj2 == null)
return false;
var result = string.Compare(obj1.Name, obj2.Name,
StringComparison.CurrentCultureIgnoreCase);
return result == 0;
}
public int GetHashCode(ProfileAttributes obj)
{
return obj.Name.GetHashCode();
}
}
Now you can process the elements in the incomingAttributes that have a name equal to an instance inside the defaultAttributes and change the property Include to true
var result = incomingAttributes
.Where(x => defaultAttributes.Contains(x, new ProfileAttributesComparer()))
.Select(p => { p.Include = true; return p;});
The result variable contains only the items with the same name but the incomingAttributes list still has three items and two of them have the Include property changed to true.
I have a document with the following domain model:
class Entity
{
...
Dictionary<string, string> Settings { get; set; }
...
}
And there's a need to update specified collection. But not override - merge with incoming updates. As I need to process thousands of documents in that manner, I choosed the PatchCommand for better performance. Got following:
new PatchCommandData
{
Key = upd.EntityId,
Patches = new[]
{
// Foreach incoming Setting remove existing value (if any) and add the new one
new PatchRequest
{
Type = PatchCommandType.Modify,
Name = nameof(Entity.Settings),
Nested = upd.UpdatedSettings.Keys
.Select(x => new PatchRequest
{
Type = PatchCommandType.Unset,
Name = x
})
.ToArray()
.Union(upd.UpdatedSettings.Keys
.Select(x => new PatchRequest
{
Type = PatchCommandType.Set,
Name = x,
Value = upd.UpdatedSettings[x]
})
.ToList())
.ToArray(),
Value = RavenJToken.FromObject(upd.UpdatedSettings)
}
}
}
This way next update is performed:
Before: { Setting1 = "Value1", Setting2 = "Value2" }
Update request: { Setting2 = "NewValue2", Setting3 = "Value3" }
After: { Setting1 = "Value1", Setting2 = "NewValue2", Setting3 = "Value3" }
But.. There's always a "but". If there is a document without Settings property in db, provided patch will raise an error saying "Cannot modify value from Settings because it was not found".
I can't find any option to switch patch mode to Set vs. Modify on the fly. And there is no option to load all documents, apply the update on application's side and update thousands of documents.
The only reasonable option I can see is to create Dictionary instance for Settings property in the class constructor.
Folks, can you advice some other options?
P.S. RavenDB version is limited to 3.5
Done. You can find the final version below. Actually, the ScriptedPatchCommandData was used. It contains JavaScript body, that Raven will evaluate and execute against documents. Potentially, it isn't the best option from the performance point of view. But nothing critical as it turned out. Hope it may be helpful for someone.
const string fieldParameterName = "fieldName";
const string valueParameterName = "value";
var patch = updates
.Select(update => new ScriptedPatchCommandData
{
Key = update.EntityId,
Patch = new ScriptedPatchRequest
{
Script = $"this[{fieldParameterName}] = _.assign({{}}, this[{fieldParameterName}], JSON.parse({valueParameterName}));",
Values = new Dictionary<string, object>
{
{
fieldParameterName,
nameof(Entity.Settings)
},
{
valueParameterName,
JsonConvert.SerializeObject(update.UpdatedSettings)
}
}
}
})
.ToList();
return await DocumentStore.AsyncDatabaseCommands.BatchAsync(patch);
I deserialized a JsonResponse using the below code.
var data = (JObject)JsonConvert.DeserializeObject(jsonResponse);
I got the response string which looks something like this
{
"results":[
{
"url":"tickets/2063.json",
"id":20794,
"subject":"Device not working",
"created_date": "2018-01-10T13:03:23Z",
"custom-fields":[
{
"id":25181002,
"value":34534
},
{
"id":2518164,
"value":252344
}
]
}
]
}
My objective is to read certain fields in this array of json objects and insert into a database. The fields i require are id, subject, created_date, member_id.
The member id is part of the custom fields. member_id is the value where id=2518164. I've used List to store this, can you let me know if List or Dictionary is better for this case. How to implement a dictionary
var data = (JObject)JsonConvert.DeserializeObject(jsonResponse);
var tickets = data["results"].ToList();
foreach (var ticketItem in tickets){
Int64? ticketFormId = ticketItem["id"].Value<Int64>();
string subject = ticketItem["subject"].Value<string>();
DateTime createdDate = ticketItem["created_date"].Value<DateTime>();
//Do you think for the next step a dictionary is better or a List is better, since I want to search for a particular id=2518164
var fieldsList = ticketItem["fields"].ToList();
foreach(var fieldItem in fieldList){
Int64? fieldId = fieldItem["id"].Value<Int64>();
if(fieldId!=null && fieldId == 2518164){
memberId = fieldItem["value"].Value<string>();
}
}
}
If you're next step to insert them all into the database, just store them into a list. A dictionary is only useful to search the item by a key.
You can also use linq to process the json in a simpler way:
var tickets = JObject.Parse(jsonResponse)["results"]
.Select(ticket => new
{
Id = (long)ticket["id"],
Subject = (string)ticket["subject"],
CreatedDate = (DateTime)ticket["created_date"],
MemberId = (long)ticket["custom-fields"]
.FirstOrDefault(cf => (int)cf["id"] == 2518164)
?["value"],
})
.ToList();
i'm trying to replicate the view of a mailbox, i try to use references and threads but don't work, somethimes thread has uniqueid null.
foreach (var rfr in Message.References ?? new MimeKit.MessageIdList())
{
var _uids = Imap.Inbox.Search(SearchQuery.HeaderContains("Message-Id", rfr));
if (_uids.Count > 0)
{
var _messages = Imap.Inbox.Fetch(_uids.ToList(), MessageSummaryItems.Envelope | MessageSummaryItems.Flags).OrderByDescending(o => o.Date);
foreach (var msg in _messages)
{
_Added.Add(msg.UniqueId);
RequestModel _model = new RequestModel
{
Address = msg.Envelope.From.Mailboxes.FirstOrDefault().Name ?? msg.Envelope.From.Mailboxes.FirstOrDefault().Address,
Subject = msg.Envelope.Subject,
Date = msg.Date.ToLocalTime().ToString(),
IsSeen = msg.Flags.Value.HasFlag(MailKit.MessageFlags.Seen),
Childs = new List<Scratch.MainWindow.RequestModel>(),
};
_retValue.Add(_model);
}
}
}
var _messages = _imapClient.Inbox.Fetch(_uids.ToList(), MessageSummaryItems.Envelope | MessageSummaryItems.Flags | MessageSummaryItems.References).OrderByDescending(o => o.Date).Take(50);
var _threads = MessageThreader.Thread(_messages, ThreadingAlgorithm.References);
The MessageThreader class uses the References (which contin a list of Message-Id values tracing back to the root of the thread) in order to construct the tree of messages. Obviously, if the list of message summaries that you give to the MessageThreader are missing some of those references, then the returned tree will have some empty nodes. This is why some of said nodes have a null UniqueId value.
FWIW, a few tips for you:
Don't do _uids.ToList() - _uids is already an IList<UniqueId>, why duplicate it for no reason?
It's more efficient to use the orderBy argument to MessageThreader.
Like this:
var orderBy = new OrderBy[] { OrderBy.ReverseDate };
var threads = MessageThreader.Thread (summaries, ThreadingAlgorithm.References, orderBy);
As the title states I'm attempting to access the first object within a nested BsonArray. I'm new to mongodb so still trying wrap my head around it.
Anyways, lets say I have the following data:
{
"_id" : ObjectId("111111dasd1asdasd1asd1"),
"data" : [
{
"name":"John Smith",
"push ups":20
},
{
"name":"John Smith",
"push ups":22
},
{
"name":"John Smith",
"push ups":25
}
]
}
I'm attempting to create a new BsonDocument by taking _id and name where I give the query a parameter for _id but I just grab whatever is in name without giving it a parameter.
ie.
{
"connect":ObjectId("111111dasd1asdasd1asd1", //assigning the value of id from the original document to this field
"name":"John Smith"
}
Since my mongo collection is structured so that each Bsondocument only deals with one unique name, I do not want to loop through the data BsonArray of my original document example. I just want to access whatever value name has and move on.
Here is my current code attempting to create a new object from values of id and name from the BsonDocument
(NOTE: This is within a method where an id param is provided).
var query = Query.EQ("_id", id);
var tempRecord = existing.FindOne(query);
var record = new
{
name = tempRecord["data"]["name"],
connect = id
};
result = record.ToBsonDocument();
return result;
At the moment tempRecord is correctly storing the data returned from the query I'm passing it. However how would I properly access the name field of the data array within tempRecord?
If you want to retrieve each name of the same _id, here's the code, if you want to change it a little bit, just leave a comment :
first of all,you should change your _id value,it's invalid, put for example : ObjectId("56d18c7e5e4b3c416e7b408b")
database => test | collection => myCollection
MongoClient client = new MongoClient();
var db = client.GetDatabase("test");
var collection = db.GetCollection<BsonDocument>("myCollection");
var filter1 = Builders<BsonDocument>.Filter.Eq("_id", new ObjectId("56d18c7e5e4b3c416e7b408b"));
using (var cursor = await collection.FindAsync(filter1))
{
while (await cursor.MoveNextAsync())
{
var batch = cursor.Current;
foreach (var document in batch)
{
int subDocMax = document[1].AsBsonArray.Count;
for(int i=0;i<subDocMax;i++)
{
MessageBox.Show("Name :"+document[1][i][0].ToString());
}
}
}
}