Jump to content
Sign in to follow this  
Gistya Eusebio

How to delete a JSON object's member?

Recommended Posts

In LSL how would you go about deleting a member from a JSON object? Have to use the Json2List functions, right? It seems they gave us getters and setters of values, but no way to easily delete members.

Share this post


Link to post
Share on other sites

llJsonGetValue and llJsonSetValue work, but they seem to be limited for what I needed.

Funny thing though, a Json object is just a string. You can easily use a do loop to populate the string as necessary, or llSubStringIndex to find a particular field in the string and modify it to your liking.

 

Format is something like:

{ Entry1:[Var1 for Entry1, Var2 for Entry1, ..., VarN for Entry1], Entry2:[Var1 for Entry2, ..., VarN for Entry2] }

Can populate your list using llJsonSetValue or converting a list and then use llOwnerSay to report the Json string. Gives you an idea how the data is stored.

I also found some good info here:

http://wiki.secondlife.com/wiki/Json_usage_in_LSL

Share this post


Link to post
Share on other sites

As Charliedru rightly points out, a lot of "standard" functions (such as getAllKeys, sortKeys, sortArraybyValue, addElement and deleteElement) are missing. So we have to roll our own. Fortunately, that can be done with the get and set functions that we do have, it just requires some thought on how the JSON object is constructed (http://json.org/ and http://wiki.secondlife.com/wiki/Json_usage_in_LSL ) and a familiarity with the LSL list functions (http://wiki.secondlife.com/wiki/List ). And at least, once a getAllKeys function has been worked out, it can be reused in other code.

So, the short geeky answer: One deletes a member/element, which may be a leaf node or an entire branch, the same as you would add one to an existing JSON object- by "getting" its parent node, converting that to a list with llJson2List, manipulating that list to delete or add what you wish, converting the new list back to a JSON object with llList2Json and then "setting" its parent node with that result.

The main thing here is that the parent node may be either a JSON_ARRAY or a JSON_OBJECT and the list manipulation you do differs accordingly. If it is a JSON_ARRAY, it's pretty straight forward, you'll just remove the list element from the list at the specified index. However the JSON_OBJECT is a bit trickier because you are now dealing with a 2-strided list. First you have to find the index of the KEY within the list and then remove both that AND the next higher index (which is the associated VALUE of the pair) as well.

Be very careful doing this! Decide whether you are deleting a leaf node or pruning an entire branch from your data structure tree (http://en.wikipedia.org/wiki/Tree_(data_structure) ) and ensure your code will run correctly after the deletion. Once you do the deletion, it's gone for good and you don't want your code to later step off a cliff by going into the JSON object and failing to find what it expected to be there.

 

// Function: string deleteJsonElement( string jsonSource, list pathtoElement );// Version 1.0 by LepreKhaun, 6/21/2013. Free to copy, modify and use as one wishes with this comment included.// This function takes a JSON object and returns a copy of it with a specified element removed from it.// jsonSource is the JSON object to remove an element from.// pathToElement is a List containing the transversal path to the element// (as defined as "specifiers" in http://wiki.secondlife.com/wiki/Json_usage_in_LSL )// AND ending with the value to remove.// If the parent of the element is a JSON_ARRAY, the value to be removed must be an integer INDEX // If the parent of the element is a JSON_OBJECT, the value to be removed must be a string KEY//// This code is not optimized nor does it contain any error checking!!! // Add whatever you feel is needed to ensure correct operation of your script.string deleteJsonElement( string jsonSource, list pathtoElement ) {		integer placeinlist;		// Obtain the JSON object of the parent of the element	string jsonParent = llJsonGetValue( jsonSource, llList2List( pathtoElement, 0, -2 ) );		// Convert it to a list	list listParent = llJson2List( jsonParent );		// Do test	if (llJsonValueType( jsonParent, [] ) == JSON_ARRAY)	{		placeinlist = llList2Integer( pathtoElement, -1 );		listParent = llDeleteSubList( listParent, placeinlist, placeinlist );				// convert to a JSON object		jsonParent = llList2Json( JSON_ARRAY, listParent );			} 	else	// We are dealing with a JSON_OBJECT	{		placeinlist =  llListFindList( listParent, llList2List( pathtoElement, -1, -1 ) );		listParent = llDeleteSubList( listParent, placeinlist, placeinlist + 1 );				// convert to a JSON object		jsonParent = llList2Json( JSON_OBJECT, listParent );	}	// Insert our results and return	return llJsonSetValue( jsonSource, llList2List( pathtoElement, 0, -2 ), jsonParent ); }

 

It is left as an exercise for the reader to construct the matching function, addJsonElement. Hope that helps!

 

[Edited to correct an error in camelCase in code. Sorry!]

Share this post


Link to post
Share on other sites

Just  sets to null its value :

 jsonString= llJsonSetValue(jsonString, ["field"] , JSON_NULL  );

 

 

It makes sense :

suppose you have got an object 

{

     field1 : 3.1415926 ,

     field2 : [ 1 ,2,3,4 ],

     field3 : "hello world"

}

 

You want to delete the field2 .

It sgenerally better to get 

{

     field1 : 3.1415926 ,

     field2 :null,

     field3 : "hello world"

}

and not

{

     field1 : 3.1415926 ,

     field3 : "hello world"

}

Because the next times you will try to fetch values from your JSON string , you couldn t know which fields ( keys if you prefer ) your JSON string has . So you will get JSON_INVALID without to know why ; without to know if your JSON string was badly parsed , or if your accessors/keys/specifiers  (field2) was incorrect

Share this post


Link to post
Share on other sites

OK, say you had an application that was designed to handle messages and you wanted to handle them in the order they came in but at times they might pile up on you, such as when you're offline. To do this, you would be implementing what is known as a queue (http://en.wikipedia.org/wiki/Queue_(data_structure) ), where you are adding messages to the end of a list and handling them from the beginning, the same way a check out line works- the person at the head of the line is next, new arrivals join the end of it.

 

Since the order of arrival is important, you would naturally use an indexed array, the JSON_ARRAY for this, adding incoming messages to the end of it and removing from the first of the array as you answer each as you get to it.

 

The key word in the action is removing, because if you are just setting the value to null, your list will grow [EDITed here for correctness] indefinitely until a stack heap collision occurs instead of simply expanding and contracting.

Share this post


Link to post
Share on other sites

Here was my solution:

 

//only works on JSON objects, not JSON arrays.string removeFromJsonObject(string json, string member) {    list data = llJson2List(json);    integer x;    if(~x = llListFindList(data,[member]))        data = llDeleteSubList(data,x,x+1);    return llList2Json(JSON_OBJECT,data);}

 

The point of what I'm doing is to completely remove the member form the object, not just set its value to null. 

Of course if it is an array then instead of doing x+1 you just do x on line 6 for llDeleteSubList's third argument.

Share this post


Link to post
Share on other sites


LepreKhaun wrote:

[snip]


else // We are dealing with a JSON_OBJECT { placeinlist = llListFindList( listParent, llList2List( pathtoElement, -1, -1 ) ); listParent = llDeleteSubList( listParent, placeinlist, placeinlist + 1 ); // convert to a JSON object jsonParent = llList2Json( JSON_OBJECT, listParent ); }

It is left as an exercise for the reader to construct the matching function, addJsonElement. Hope that helps!

 

[Edited to correct an error in camelCase in code. Sorry!]

The problem with the above is that if the specified member does not exist, you will erroneously delete the first and last members of the list, because the llListFindList will return -1, which will cause llDeleteSubList to act from -1 to 0. I know you said it does not contain error checking, but just making it clear: in my previous post you'll see why I made sure to only perform the deletion if the specified element exists in the list.

Share this post


Link to post
Share on other sites

Yes, we're both on the same page here. My approach might require a call to a (not supplied by LSL!) function "isKey", that would return a boolean after testing to see if the supplied KEY is, in fact, within a specific JSON_OBJECT. However, a run time removal of any element, either a KEY:VALUE pair or a VALUE within an indexed list, implies some logic thread leading up to it that one would hope is correctly written.

 

But our main problem with Json usage is that LSL lacks native support for both associated array and nested data structure manipulation and we have to "roll our own" using the llList* functions. Klutzy, but doable. A bit of study of Computer Science doesn't hurt either, very little math involved even! :smileyhappy:

 

[ETA] On a side note, FORTRAN got nested arrays in 1954. Such is progress...

Share this post


Link to post
Share on other sites


LepreKhaun wrote:

[snip] 

But our main problem with Json usage is that LSL lacks native support for both associated array and nested data structure manipulation and we have to "roll our own" using the llList* functions. Klutzy, but doable. A bit of study of Computer Science doesn't hurt either, very little math involved even! :smileyhappy:

 

[ETA] On a side note, FORTRAN got nested arrays in 1954. Such is progress...

Is there some technical reason why they continue to leave arrays and nesting out of LSL? And for that matter, data storage? They could easily charge a nominal fee for database usage and pad profits with that. As it is we have to rely on outside data stores when most of the time, it's over-kill.

Maybe they're working on enhancing LSL in these ways, but we just haven't seen it yet.

Share this post


Link to post
Share on other sites


Gistya Eusebio wrote:


LepreKhaun wrote:

[snip] 

But our main problem with Json usage is that LSL lacks native support for both associated array and nested data structure manipulation and we have to "roll our own" using the llList* functions. Klutzy, but doable. A bit of study of Computer Science doesn't hurt either, very little math involved even! :smileyhappy:

 

[ETA] On a side note, FORTRAN got nested arrays in 1954. Such is progress...

Is there some technical reason why they continue to leave arrays and nesting out of LSL? And for that matter, data storage? They could easily charge a nominal fee for database usage and pad profits with that. As it is we have to rely on outside data stores when most of the time, it's over-kill.

Maybe they're working on enhancing LSL in these ways, but we just haven't seen it yet.

Ahhhh, but that's just thing- JSON actually has brought true, indexed arrays as well as associated arrays AND nesting to LSL! The only thing lacking is the methods to work on them naturally. So, we'll have to "roll our own".

 

Fortunately, using just the newly supplied JSON methods of llJsonGetValue, llJsonSetValue, llList2Json, llJson2List and LlJsonValueType along with the llList* functions we do have, we can not only construct complex data objects but craft the methods to handle them as well.

 

OOP has come to LSL! woot woot!!!

Share this post


Link to post
Share on other sites

Where have you seen that  "JSON actually has brought true, indexed arrays as well as associated arrays AND nesting to LSL"

It s wrong .

With an indexed array , get operations or set operations are O(1) time .Time is independant of the size of your datas.

With map arrays , get operations or set operations are O(k) time . Time is proportionnal to the size of the index of your datas. Time is independant of the size of the values of your datas

With a true list from an another language ( not LSL list ), get operations or set operations are too proportinnal to the sum of the size of node pointers or references . Time is independant of the size of the values of your datas

With JSON string inside LSL  ,  get operations or set operations are O(n) time . Time is proportionnal to the size of your FULL datas ( keys and values ) , because it s as simple silly string. It s the worst case who can happen .

I dont consider JSON as a solution  to replace indexed arrays and associated arrays , and nested arrays . The goal to use indexed arrays , associated arrays , nested arrays is to have a better data structure more efficient for some kind of algorithms , not a better syntax .  And binary searchs can t be implemented efficiently , by instance , because there is no string operators to compare s1 < s2 or s1 >s2 . The "keys" of the JSON string are only strings .  JSON strings in LSL  are the worst efficient to use algorithms

 You have talked too about nested data structures : same problem .  a real data structure cfrom an another language  could implement AVL , or binary trees and to be able to do search  in O(ln(n)) time .  With JSON String, as it s implmented , it will be never possible

 

Share this post


Link to post
Share on other sites


note Genesis wrote:

Where have you seen that  "JSON actually has brought true, indexed arrays as well as associated arrays AND nesting to LSL"

It s wrong .

With an indexed array , get operations or set operations are O(1) time .Time is independant of the size of your datas.

With map arrays , get operations or set operations are O(k) time . Time is proportionnal to the size of the index of your datas. Time is independant of the size of the values of your datas

With a true list from an another language ( not LSL list ), get operations or set operations are too proportinnal to the sum of the size of node pointers or references . Time is independant of the size of the values of your datas

With JSON string inside LSL  , 
 get operations or set operations are O(n) time . Time is proportionnal to the size of your FULL datas ( keys and values ) , because it s as simple silly string. It s the worst case who can happen .

I dont consider JSON as a solution  to replace indexed arrays and associated arrays , and nested arrays . The goal to use indexed arrays , associated arrays , nested arrays is to have a better data structure more efficient for some kind of algorithms , not a better syntax .  And binary searchs can t be implemented efficiently , by instance , because there is no string operators to compare s1 < s2 or s1 >s2 . The "keys" of the JSON string are only strings .  JSON strings in LSL  are the worst efficient to use algorithms

 You have talked too about nested data structures : same problem .  a real data structure cfrom an another language  could implement AVL , or binary trees and to be able to do search  in O(ln(n)) time .  With JSON String, as it s implmented , it will be never possible

 

No denying the shortcomings of LSL that all of us have to struggle with. However, the definition of indexed and associative arrays does not include the stricture of amortized time of operations as you give it* except at the lowest level of optimized implementation. In other words, you are citing "best case" implementations, which is rarely seen in high level languages, even those with native support built in for these structures (which, as I've already pointed out, LSL lacks).

 

And I am in complete agreement with "I dont consider JSON as a solution  to replace indexed arrays and associated arrays , and nested arrays ." since LSL has none of that to replace in the first place, it is what JSON brings to LSL.

 

But when you claim "there is no string operators to compare s1 < s2 or s1 >s2" you are apparently unaware of the numerous user functions that have been written to do exactly that. Just Google "LSL string compare" and pick the one you feel is most memory or time efficient as you wish.

 

And I'll grant that " a real data structure cfrom an another language  could implement AVL , or binary trees and to be able to do search  in O(ln(n)) time .  With JSON String, as it s implmented , it will be never possible". However that is far from saying that we can't now implement AVL or binary trees if we so choose. In fact, with a bit of thought, one can now do true objects with encapsulated data that can only be manipulated with methods. A bit more thought, and one can even implement classes, complete with inheritance, albeit somewhat clumsily since LSL has no pointers and can't pass by reference. I'm of the opinion that a coder shouldn't avoid complex data structures simply because "it may take a bit more time" to do something and, as a consequence, miss out on such powerful tools as reusable code modules.

 

For instance, say one wished to now make a Schedule application for SL, a way to store alarms that would not only go off when as the user has set them, but would also give them a message with the particulars of the alarm- say, "Builder's Brewery class in Advanced Bulding in 5 minutes. Don't miss this!" Wouldn't that be neat?

 

First you'd design an Alarm object, possibly like this:

jsonAlarm {	"kID": string,		// Unique Identifier	"kTIME": string,	// Time Stamp Format - "YYYY-MM-DDThh:mm:ss.ff..fZ"	"kTYPE": string,	// Descriptive classification of the alarm, optional	"kPURPOSE": string,	// Reason for the alarm being set, optional	"kSLURL": string	// "Region Name/x/y/z" SLURL, optional}

 

Then, you'd design your Schedule object along these lines:

jsonSchedule{	"kCURRENT_ALARMS: {		"kTHIS_DAY": [jsonAlarm, ... ],		"kTHIS_WEEK": [jsonAlarm, ... ],		"kTHIS_MONTH": [jsonAlarm, ... ],		"kTHIS_YEAR": [jsonAlarm, ... ]	}	"kONE_TIME_ALARMS": [jsonAlarm, ... ],	"kRECURRING_ALARMS": {		"kDAILY_ALARMS": [jsonAlarm, ... ],		"kWEEKLY_ALARMS": [jsonAlarm, ... ],		"kMONTHLY_ALARMS": {			"kMONTHLY_ALARMS_BY_DATE": [jsonAlarm, ... ],			"kMONTHLY_ALARMS_BY_DAY_OF_WEEK" : [jsonAlarm, ... ]		},		"kYEARLY_ALARMS": [jsonAlarm, ... ]	}}

 

The next step would be deciding on the functionality you'll present to the user, the "methods" of the Schedule object. And these might be along the lines of:

VIEW one or more Alarms	// VIEW Modifiers	ALL- returns a formatted listing of all "specified" alarms in Schedule	NEXT- returns next scheduled alarm	TODAY- returns all alarms for today	DATE- returns all alarms for the specified date	FIND a specific AlarmADD an AlarmDELETE an AlarmMODIFY an Alarm

 Then it's simply a matter of writing the code bits to do the methods you've decided on, and having a (say) 10 minute timer event that would continuously update your Schedule object (copying recurring alarms into THIS_DAY as needed, for instance) and setting the timer for the next scheduled alarm.

 

The beauty of this approach is that later you may decide to add a field/key to the jsonAlarm object named "HOW_TO_NOTIFY" and have choices such as having a prim flashing its color, a sound played, an IM or e-mail sent, llOwnerSay, whatever. And, get this, you won't need to change anything you've written up to that point but simply add the coding to handle that field!

 

But wait, there's more! Once you've finished your Schedule object, you can now reuse it! For instance, you could make a completely menu-based front end, or a HUD with some buttons, a clock and perpetual calendar used to access the object's methods (along with the needed llTextBoxes at times) or a wall-mounted display the same along the lines of the HUD. These would be three entirely different applications to offer the end user, but they would all have the majority of code exactly the same! And, once they're written, if you decide at some point to change the data structure of either your Schedule object or the Alarms (such as shortening the names of all your keys to have a smaller memory footprint of the JSON object), you only need to change the coding within the object itself, not in any of the front ends as long as you leave the method signatures themselves untouched. woot woot!

 

* FYI, the best case for  map (associated or random access) arrays' "get" and "set" is not 0(k) as you stated but is 0(log k) 0(1). Since the indexed arrays are dynamic (as opposed to static) the big 0 values are 0(n) for anything but indexing, which is 0(1).

  [EDIT TO CORRECT SELF] Sorry, big 0 notation can be somewhat confusing if one mixes best case and worse case considerations in together. [ETA: source citation for best case implementations: http://en.wikipedia.org/wiki/Array_data_structure table under Efficiency comparison with other data structures.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

×
×
  • Create New...