Access Rhino's native JSON.Stringify from Java - rhino

Is there a cleaner way to get the JSON representation of a Javascript object than with the following kludge?
System.out.println(((ScriptableObject) scope).callMethod(
cx, (Scriptable) scope.get("JSON", scope),
"stringify", new Object[]{jsObject}));
Where jsObject is the ScriptableObject I want to stringify.

Note that Hannes has now addressed this in Rhino. So the usage simplifies to this:
import org.mozilla.javascript.NativeJSON;
// ...
Object json = NativeJSON.stringify(cx, scope, jsObject, null, null);
The org.mozilla.javascript.NativeJSON class should be public in the Rhino 1.7R4 release.

I was able to get this working within an Apache Ant target using the NativeJSON class.
importPackage(org.mozilla.javascript);
var context = Context.enter();
var json = '{}';
// The call to parse required a reviver function that should return the
// state of a key/value pair.
var reviver = function(key, value) { return value; };
var scope = context.initStandardObjects();
var object = NativeJSON.parse(context, scope, json, reviver);
// The call to stringify does not require the replacer or space parameters.
// The replacer is a function that takes a key/value pair and returns the
// new value or an array of keys from the input JSON to stringify. The space
// parameter is the indentation characters or length.
json = NativeJSON.stringify(context, scope, config, null, 4);
http://mozilla.github.io/rhino/javadoc/org/mozilla/javascript/NativeJSON.html
https://github.com/mozilla/rhino/blob/master/src/org/mozilla/javascript/NativeJSON.java

Related

Json.Net - Immediate parent path not being returned

I am attempting to retrieve the path of the immediate parent of a JToken object found via SelectToken.
grandparent
parent
object
In above structure the value of object.Path is "grandparent.parent.object" and the value of object.Parent.Path is also "grandparent.parent.object".
Is this a bug or should the path of a parent be retrieved in another way?
Below is an example that illustrates object.Path and object.Parent.Path being the same:
var input = "{'grandparent': { 'parent' : {'object' : 'value'}}}";
var jsonInput = JObject.Parse(input);
var jsonObject = jsonInput.SelectToken("..object");
var path = jsonObject.Path; //grandparent.parent.object
var parentPath = jsonObject.Parent.Path; //grandparent.parent.object (same as object)
var realParentPath = jsonObject.Parent.Parent.Path; //grandparent.parent (actual parent path)
You have stumbled on an implementation detail of Json.NET, which is that it models a JSON object with two levels of container, namely the JObject which contains a collection of JProperty items, each of which in turn contains the actual property value:
JObject // A JSON object: an unordered set of name/value pairs
-> IEnumerable<JProperty> Properties()
JProperty // A property name/value pair
-> string Name // The property name
-> JToken Value // The property value
I.e., using the diagram for an object from https://json.org/:
The JObject corresponds to the entire section between braces, and the JProperty corresponds to a specific string : value portion.
I reckon this implementation was chosen to separate the name from the value, so that JValue could be used for both array and object primitive values, without having to add in a meaningless Name property for array items. However, from the point of view of SelectToken, the existence of JProperty is a bit awkward because it doesn't correspond to anything selectable via a JSONPath query since SelectToken always returns the actual value rather than the container property. Newtonsoft chose to make JProperty.Path the same as it's value's path; possibly they could have chosen to make JProperty.Path throw an exception instead, but they did not.
To hide this implementation detail, you could introduce an extension method SelectableParent():
public static partial class JsonExtensions
{
public static JToken SelectableParent(this JToken token)
{
if (token == null)
return null;
var parent = token.Parent;
if (parent is JProperty)
parent = parent.Parent;
return parent;
}
}
Then use it as follows:
var path = jsonObject.Path; //grandparent.parent.object
var parentPath = jsonObject.SelectableParent().Path; //grandparent.parent
Demo fiddle here.
Related: Why does AddAfterSelf return 'JProperty cannot have multiple values' when used with SelectToken?.
The following practical example helped my understanding of the differences between JValue and it's JProperty parent.
var input = "{'grandparent': { 'parent' : {'object' : 'value', 'object2': 'value2'}}}";
var jsonInput = JObject.Parse(input);
var jsonObject = jsonInput.SelectToken("..object");
//value
var jsonParentObject = jsonObject.Parent;
//"object": "value"
var jsonParentParentObject = jsonObject.Parent.Parent;
//{
//"object": "value",
//"object2": "value2"
//}
var jsonParentParentParentObject = jsonObject.Parent.Parent.Parent;
//"parent": {
// "object": "value",
// "object2": "value2"
//}

How does the Dart URI class QueryParameters handle Map values?

According to the documentation, it needs to follows the Form Post rules at: https://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4. When looking at that information it did not give me much to work with in terms of complex objects or maps.
Right now, If I have a list for example: Each item in the list needs to be stringified.
var params = {"list": [1,2,3]};
// needs to be stringed.
params["list"] = params["list"].map((item)=>item.toString()).toList();
Simple. Also all base items need to be a string as well
var params = {"number": 1, "boolean": true};
params = params.forEach((k,v)=> params[k].toString());
But how do we handle maps?
var params = {"map": {"a":1,"b":"foo","c":false,"d":[]}};
// ??
It seems that after testing in my app and in dart pad, you need to make sure everything is strings, so i am trying to come up with a way to effectively cover lists, maps, and maybe more complex objects for encoding.
var params = {};
params["list"] = [1,2,3];
params["number"] = 1;
params["boolean"] = true;
params["map"] = {"a":1,"b":"foo","c":false,"d":[]};
params.forEach((String key, dynamic value){
if(value is List){
params[key] = value.map((v)=>v.toString()).toList();
}else if(value is Map){
// ????
}else{
params[key] = value.toString();
}
//maybe have an additional one for custom classes, but if they are being passed around they should already have their own JSON Parsing implementations.
}
Ideally, the result of this would be passed into:
Uri myUri = new Uri(queryParameters: params);
and right now, while i solved the list issue, it doesn't like receiving maps. Part of me just wanted to stringify the map as a whole, but i wasn't not sure if there was a better way. I know that when someone accidentally stringified the array, it was not giving me: ?id=1&id=2 but instead ?id=%5B1%2C2%5D which was not correct.
I don't think there is any special support for maps. Query parameters itself is a map from string to string or string to list-of-strings.
Everything else need to be brought into this format first before you can pass it as query parameter.
A simple approach would be to JSON encode the map and pass the resulting string as a single query parameter.

Getting all l10n values stored in localized bundle

I'm building a FF extension, and I'm processing some xhtml for myself in order to supporn subforms loading, so I have to identify the elements with l10n attributes defined and add them the string value. Because the l10n can't be shared from main code to content scripts (because isn't a simple JSON object), I managed the situation by getting the loaded keys values and defining an "localized array bundle", like this:
lStrings = ["step_title", ........ ];
for (var i = 0; i < lStrings.length; i++) {
bundle[lStrings[i]] = this.locale(lStrings[i]);
}
The thing is, I have to write here every entry in the .properties files... SO, do you know how to access this key values? I already tryed with .toString .toLocalString and inspecting the object, but can't find the way the object to be capable of returning all the key collection.
Do you have a better idea for improvement?
var yourStringBundle = Services.strings.createBundle('chrome://blah#jetpack/content/bootstrap.properties?' + Math.random()); /* Randomize URI to work around bug 719376 */
var props = yourStringBundle.getSimpleEnumeration();
// MDN says getSimpleEnumeration returns nsIPropertyElement // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIStringBundle#getSimpleEnumeration%28%29
while (props.hasMoreElements()) {
var prop = props.getNext();
// doing console.log(prop) says its an XPCWrappedObject but we see QueryInterface (QI), so let's try QI'ing to nsiPropertyElement
var propEl = prop.QueryInterface(Ci.nsIPropertyElement);
// doing console.log(propEl) shows the object has some fields that interest us
var key = propEl.key;
var str = propEl.value;
console.info(key, str); // there you go
}
See comments for learning. Nice quesiton. I learned more about QI from replying.

Making a variable the key in a Map

I have two classes
class Phone extends Observable
{
#observable String type = '';
#observable String provider = '';
#observable String num = '';
Map<String, Map<String, String> map = {};
Phone() {}
Phone.build({ this.type,
this.provider,
this.num });
}
I have attempted to use the field values as the key in map like so
Phone phone = new Phone();
phone.map[phone.type] = {'type':'cell', 'provider':'Verizon', 'num':'1234567'};
but it does not work. How can I make the fields value the key for the map?
just remove the quotes
phone.map[phone.type] = {type:'cell', provider:'Verizon', num:'1234567'};
Because you are using strings in your example this may not apply but be aware that if you use instances of custom types as Map key...
The keys of a `HashMap` must have consistent [Object.operator==]
and [Object.hashCode] implementations. This means that the `==` operator
must define a stable equivalence relation on the keys (reflexive,
anti-symmetric, transitive, and consistent over time), and that `hashCode`
must be the same for objects that are considered equal by `==`.

Transform data when parsing a JSON string using Dart

I'm using the parse() function provided in dart:json. Is there a way to transform the parsed data using parse()? I'm thinking of something similar to the reviver argument when parsing JSON using JavaScript:
JSON.parse(text[, reviver])
The parse() function in dart:json takes a callback as an arg that you can use to transform the parsed data. For example, you may prefer to express a date field as a DateTime object, and not as a list of numbers representing the year, month and day. Specify a ‘reviver’ function as a second argument to parse.
This function is called once for each object or list property parsed, and the return value of the reviver function is used instead of the parsed value:
import 'dart:json' as json;
void main() {
var jsonPerson = '{"name" : "joe", "date" : [2013, 10, 3]}';
var person = json.parse(jsonPerson, (key, value) {
if (key == "date") {
return new DateTime(value[0], value[1], value[2]);
}
return value;
});
person['name']; // 'joe'
person['date'] is DateTime; // true
}

Resources