Create JArray from IEnumerable<JToken>, BUT PRESERVE REFERENCES [duplicate] - json.net

Using the depreciated System.Json, I get the Result I expect (coming from Javascript):
The Child gets a GrandChild and all the Parents know about it...
var Parents = new JsonObject();
var Children = new JsonObject();
var Parent = JsonArray.Parse("[]");
Parents.Add("1", Parent);
var Child = JsonArray.Parse("[]");
Children.Add("1", Child);
var DstParent = (JsonArray)Parents["1"];
DstParent.Add(Children["1"]);
var DstChild = (JsonArray)Children["1"];
JsonObject GrandChild = (JsonObject)JsonArray.Parse("{}");
GrandChild.Add("Age", 15);
DstChild.Add(GrandChild);
var Result = Parents.ToString();
Gives me: "{"1":[[{"Age":15}]]}"
Using Newtonsoft.Json 6.0.8, The Parent is not getting the "hint" that it's Child got a GrandChild.
var Parents = new JObject();
var Children = new JObject();
var Parent = JArray.Parse("[]");
Parents.Add("1", Parent);
var Child = JArray.Parse("[]");
Children.Add("1", Child);
var DstParent = (JArray)Parents["1"];
DstParent.Add(Children["1"]);
var DstChild = (JArray)Children["1"];
var GrandChild = JObject.Parse("{}");
GrandChild.Add("Age", 15);
DstChild.Add(GrandChild);
Gives me: "{"1":[[]]}"
What am I doing wrong?

The problem arises because all JToken objects have a Parent property which records their location in the JSON object hierarchy -- but you are trying to add your JArray Child to two different unrelated parents. First you add it to the Children object (which is not actually in the tree of JSON objects you are creating):
Children.Add("1", Child);
Next you add it to the DstParent array (which is in the tree of JSON objects you are creating):
DstParent.Add(Children["1"]);
So, what does Json.NET do in this case? It could either:
Throw an exception for trying to create a multiply-parented object, OR
Excise the object from its previous parent and move it to its new parent, OR
Create a clone of the object in its new parent.
As it turns out, it takes option #3: it copies Children["1"] into DstParent. I'm not sure if or where this is documented, but it's apparent from the source code for JContainer - look for InsertItem which calls EnsureParentToken. Thus when you add your grandchild to DstChild you are adding it to the original array not the copy. You can see this by adding the following debug code:
Debug.WriteLine(object.ReferenceEquals(DstParent[0], DstChild)); //prints False
The simple fix for this is to avoid creating the Children object which is completely unnecessary anyway:
var parentObj = new JObject();
var parentArray = new JArray();
parentObj.Add("1", parentArray);
var childArray = new JArray();
parentArray.Add(childArray);
var grandChild = new JObject();
grandChild.Add("Age", 15);
childArray.Add(grandChild);

Related

Issue with relation when creating record

I have two tables that are related as follows:
PMLprojects ONE - MANY Inovice_stat
I have a script to create a record in the Invoice_stat table. It goes as follows:
var myProjectList = app.datasources.PMLprojects;
var myProjectListID = myProjectList.Id;
var myDatasource = app.datasources.Invoice_stat;
var myCreateDatasource = myDatasource.modes.create;
now = new Date();
var draft = myDatasource.modes.create.item;
draft.EmailStatus = "Yes";
draft.PaidStatus = "No";
draft.DateCreate = now;
myCreateDatasource.createItem(function(newRecord) {
var key = newRecord._key;
});
myDatasource.saveChanges();
All the fields are properly populates except the relation to PMLprojects. How can I related the record from Invoice_stat to PMLprojects? I'm getting the following message:
Error log :
com.google.apps.appmaker.client.datasource.AbstractModelDataSource
WARNING: Could not select element with key RecordKey{key=private$6,
model
key=1Y8Ijd68IZyWFllY3d_C9fhAOFtVgKCtH|Gu5LnmmFmZHfEbrL5Ug1fybNaVLSEPn6}.
No records bound.
Here is some proposed edited code for you to try. However, do remember that if your PMLprojects datasource is not loaded on the client, then this will still fail. I also highly recommend that you check out the official documentation here https://developers.google.com/appmaker/models/relations#modify_associations.
var myProjectList = app.datasources.PMLprojects.item; //change this line to point to an item in the datasource
//var myProjectListID = myProjectList.Id; This line is not necessary
var myDatasource = app.datasources.Invoice_stat;
var myCreateDatasource = myDatasource.modes.create;
now = new Date();
var draft = myCreateDatasource.item; //you already declared the create mode
draft.EmailStatus = "Yes";
draft.PaidStatus = "No";
draft.DateCreate = now;
draft.YourRelationToPMLprojects = myProjectList; //here is where you create your relation, replace YourRelationToPMLprojects with your actual relation name should show up in code autocomplete
myCreateDatasource.createItem(function(newRecord) {
var key = newRecord._key;
});
myDatasource.saveChanges();
Since you are probably using both tables with the Manual Save mode... then #MarkusMalessa's approach might return you an error. If that is so, you have to make sure that you create the relation after you create the item but before you save changes. For that, take into consideration the following example:
var project = app.datasources.PMLprojects.item; //project item
var ds = app.datasources.Invoice_stat;
var createDs = ds.modes.create;
var draft = createDs.item;
draft.EmailStatus = "Yes";
draft.PaidStatus = "No";
draft.DateCreate = new Date();
createDs.createItem(function(){
ds.item.PMLproject = project; //here is where you create your relation
ds.saveChanges();
});
Just remember, this will only work as long as the PMLprojects datasource has already been loaded, otherwise you will probably get an error.

HtmlAgilityPack select Node return all Node from document

I want to scrap data from a website when select node from a node using class All Element selected from document
this is my code..
var baseUri = new Uri("http://www.coupondunia.in/flipkart");
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
WebClient client = new WebClient();
doc.Load(client.OpenRead(baseUri));
HtmlNodeCollection div = doc.DocumentNode.SelectNodes("//div[contains(#class,'detail-coupons') and contains(#class,'inner')] ");
foreach (HtmlNode item in div)
{
var Linksx = item.SelectSingleNode("//a[contains(#class,'emptyAnchorCouponPage couponTitle couponTitle_inplaceEdit clickableCoupon')]");
var Links = item.SelectNodes("//a[contains(#class,'couponTitle') and contains(#class,'emptyAnchorCouponPage') and contains(#class,'couponTitle_inplaceEdit') and contains(#class,'clickableCoupon')]");
}
In Links and Linkx return multiple records
To tell HtmlAgilityPack that your XPath scope is limited to current item you need to add a dot/period (.) at the beginning of the XPath :
foreach (HtmlNode item in div)
{
var Linksx = item.SelectSingleNode(".//a[contains(#class,'emptyAnchorCouponPage couponTitle couponTitle_inplaceEdit clickableCoupon')]");
var Links = item.SelectNodes(".//a[contains(#class,'couponTitle') and contains(#class,'emptyAnchorCouponPage') and contains(#class,'couponTitle_inplaceEdit') and contains(#class,'clickableCoupon')]");
}
Otherwise the XPath scope considered is the entire HtmlDocument.

Tridion Core service - working with a Hierarchical Taxonomy

I'm using the Tridion Core Service (Tridion 2011 SP1) to retrieve a list of keywords for given Category ID.
CoreService2010Client client = new CoreService2010Client();
XElement xmlCategoryKeywords = client.GetListXml(category.Id,
new KeywordsFilterData());
This returns what seems to be a flat XML structure representing our taxonomy which is 4 levels deep.
The documentation details an approach for working with this:
var categoryKeywords = xmlCategoryKeywords.Elements().Select(element =>
element.Attribute("ID").Value).Select(id => (KeywordData)client.Read(id, null)
);
foreach (KeywordData keyword in categoryKeywords)
{
Console.WriteLine("\t Keyword ID={0}, Title={1}", keyword.Id, keyword.Title);
}
However this will only list each Keyword. The KeywordData object contains property ParentKeywords so it would be possible to build the hierarchy in memory.
Is it possible to retrieve XML from the Core Service with a hierarchical structure? Or an easier way to work with this data?
One way is to use TaxonomiesOwlFilterData:
string publicationId = "tcm:0-3-1";
var filter = new TaxonomiesOwlFilterData();
filter.RootCategories = new[] {new LinkToCategoryData{ IdRef = "tcm:3-158-512"},};
var list = ClientAdmin.GetListXml(publicationId, filter);
As you see it is called on publication, but you can narrow it down to one or more categories. It will return you scary XML list that you can further process like this:
XNamespace tcmc = publicationId + "/Categories#";
XNamespace rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
XNamespace tcmt = "http://www.tridion.com/ContentManager/5.2/Taxonomies#";
var taxonomyTree = new Dictionary<string, List<string>>();
var keywordNodes = list.Descendants(tcmc + "cat");
foreach (var keywordNode in keywordNodes)
{
var parents = new List<string>();
var parentNodes = keywordNode.Descendants(tcmt + "parentKeyword");
if (parentNodes.Count() > 0)
{
foreach (var parentNode in parentNodes)
{
parents.Add(parentNode.Attribute(rdf + "resource").Value);
}
}
taxonomyTree.Add(keywordNode.Attribute(rdf + "about").Value, parents);
}
As a result you will get unordered list of your keywords and corresponding parents that you can further process as you like. Item that has no parent is obviously a parent keyword. It might not be the most beatiful solution, but at least you will need only one call to server and not read each keyword.
You could process each branch, level by level. Here's some code I've been playing around with that does that:
CoreService2010Client client = new CoreService2010Client("basicHttp_2010");
KeywordsFilterData keywordsDataFilter = new KeywordsFilterData()
{
BaseColumns = ListBaseColumns.IdAndTitle,
IsRoot = true
};
UsingItemsFilterData usingItemsFilter = new UsingItemsFilterData()
{
BaseColumns = ListBaseColumns.IdAndTitle,
ItemTypes = new[] { ItemType.Keyword },
InRepository = new LinkToRepositoryData() { IdRef = "tcm:0-1-1" }
};
XElement parents = client.GetListXml("tcm:1-272-512", keywordsDataFilter);
foreach (XElement parent in parents.Descendants())
{
// Do something with the parent (top level) KW
XElement children = client.GetListXml(parent.Attribute("ID").Value, usingItemsFilter);
foreach (XElement child in children.Descendants())
{
// Do something with the child KW
}
}
I've found in the past that processing a flat list in to a hierarchy (in my case a list of all SGs in a Publication) created a massive overhead compared to processing a branch at a time. Of course I should caveat that by saying that I tried that with an old (early 5.x) version of Tridion so things may have improved since then.
Tridion 2011 SP1 comes with a new CoreService EndPoint. CoreService 2011. Its recommended to use the latest endpoint. Latest endpoint has new functionalists also bug fixes. SP1 also has a default coreservice client proxy that u can use directly in your code.

Combine/merge Dynamic Objects in AS3

I have 2 dynamic objects and I want to build one to contain all the properties:
var o1:Object = {prop1:val1,prop2:val2,prop3:val3};
var o2:Object = {prop3:val3a,prop4:val4};
and I need to obtain a third object that looks like that:
{prop1:val1, prop2:val2, prop3:val3a, prop4:val4};
Basically I need a way to iterate through the object properties and to add new properties to the third object. I have to mention I'm quite new to AS3/Flash/Flex.
First question, do you really mean to have prop3 in both objects? you will need to decide what to do in case of a collision like that, which object has precedence.
Secondly, check out the introspection apis: http://livedocs.adobe.com/flex/3/html/help.html?content=usingas_8.html
something like this should work:
public function mergeDynamicObjects ( objectA:Object, objectB:Object ) : Object
{
var objectC:Object = new Object();
var p:String;
for (p in objectA) {
objectC[p] = objectA[p];
}
for (p in objectB) {
objectC[p] = objectB[p];
}
return objectC;
}
If the property exists in A and B, B's will overwrite A's. Also note that if the values of a property is an object, it will pass a reference, not a copy of the value. You might need to clone the object in those cases, depending on your needs.
Note: I haven't actually tested the above, but it should be close. Let me know if it doesn't work.
Updated to fix the errors. Glad it works for you though.
You can dynamically access/set properties on objects with the index operator. The for loop will itterate over the property names, so if you put it all together, the following test passes:
[Test]
public function merge_objects():void {
var o1:Object = {prop1:"one", prop2:"two", prop3:"three"};
var o2:Object = {prop3:"threeA", prop4:"four"};
var o3:Object = new Object();
for (var prop in o1) o3[prop] = o1[prop];
for (var prop in o2) o3[prop] = o2[prop];
assertThat(o3.prop1, equalTo("one"));
assertThat(o3.prop2, equalTo("two"));
assertThat(o3.prop3, equalTo("threeA"));
assertThat(o3.prop4, equalTo("four"));
}
you can iterate over the object properties like:
var obj1:Object = new Object();
for(var str:String in obj2){
obj1[str] = "any value"; // insert the property from obj2 to obj1
}

ArrayCollection removing sort

After applying a numeric sort to my dataprovider(Array Collection), I can not reorder the items via a tilelist. Do I need to remove the sort from the arrayCollection. If so, is it just a case of setting collection.sort = null ?
var sortField:SortField=new SortField();
sortField.name="order";
sortField.numeric=true;
var sort:Sort=new Sort();
sort.fields=[sortField];
Setting the sort to null should indeed remove the sort for the collection. You might need to do an optional refresh().
I got caught with this problem too, I found your question, and I still didnt get it solved like Christophe suggested.
After suffering with this for a while, I discovered one way to avoid the problems you mentioned.
Simply use an auxiliary ArrayCollection to do the sort. Anyway your Sort instance seems to be temporary (you want to through it away), so why not use a temporary ArrayCollection?
Here's how my code looked like:
// myArrayCollection is the one to sort
// Create the sorter
var alphabeticSort:ISort = new Sort();
var sortfieldFirstName:ISortField = new SortField("firstName",true);
var sortfieldLastName:ISortField = new SortField("lastName",true);
alphabeticSort.fields = [sortfieldFirstName, sortfieldLastName];
// Copy myArrayCollection to aux
var aux:ArrayCollection = new ArrayCollection();
while (myArrayCollection.length > 0) {
aux.addItem(myArrayCollection.removeItemAt(0));
}
// Sort the aux
var previousSort:ISort = aux.sort;
aux.sort = alphabeticSort;
aux.refresh();
aux.sort = previousSort;
// Copy aux to myArrayCollection
var auxLength:int = aux.length;
while (auxLength > 0) {
myArrayCollection.addItemAt(aux.removeItemAt(auxLength - 1), 0);
auxLength--;
}
It's not the neatest code, it has some weird hacks like auxLength instead of aux.length (this one gave me -1 array range exception), but at least it solved my problem.
Source
Adobe Flex - Sorting an ArrayCollection by Date
/**
* #params data:Array
* #return dataCollection:Array
**/
private function orderByPeriod(data:Array):Array
{
var dataCollection:ArrayCollection = new ArrayCollection(data);//Convert Array to ArrayCollection to perform sort function
var dataSortField:SortField = new SortField();
dataSortField.name = "period"; //Assign the sort field to the field that holds the date string
var numericDataSort:Sort = new Sort();
numericDataSort.fields = [dataSortField];
dataCollection.sort = numericDataSort;
dataCollection.refresh();
return dataCollection.toArray();
}

Resources