I am working on an ASP classic project where I have implemented the JScript JSON class found here. It is able to interop with both VBScript and JScript and is almost exactly the code provided at json.org. I am required to use VBScript for this project by the manager of my team.
It works very well on primitives and classes defined within ASP. But I have need for Dictionary objects which from my knowledge are only available through COM interop. (via Server.CreateObject("Scripting.Dictionary")) I have the following class which represents a product: (ProductInfo.class.asp)
<%
Class ProductInfo
Public ID
Public Category
Public PriceUS
Public PriceCA
Public Name
Public SKU
Public Overview
Public Features
Public Specs
End Class
%>
The Specs property is a Dictionary of key:value pairs. Here's how I'm serializing it: (product.asp)
<%
dim oProd
set oProd = new ProductInfo
' ... fill in properties
' ... output appropriate headers and stuff
Response.write( JSON.stringify( oProd ) )
%>
When I pass an instance of ProductInfo to JSON.Stringify (as seen above) I get something like the following:
{
"id": "1547",
"Category": {
"id": 101,
"Name": "Category Name",
"AlternateName": "",
"URL": "/category_name/",
"ParentCategoryID": 21
},
"PriceUS": 9.99,
"PriceCA": 11.99,
"Name": "Product Name",
"SKU": 3454536,
"Overview": "Lorem Ipsum dolor sit amet..",
"Features": "Lorem Ipsum dolor sit amet..",
"Specs": {}
}
As you can see, the Specs property is an empty object. I believe that the JSON stringify method knows that the Specs property is an object, so it appends the {} to the JSON string around the stringified output. Which in this case is an empty string. What I expect it to show, however is not an empty object. See below:
"Specs": {
"foo":"bar",
"baz":1,
"etc":"..."
}
I believe the problem area of the JSON library is here: (json2.asp)
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
I postulate that the problem with the above code is that it assumes that all objects inherit from the Object class. (The one that provides hasOwnProperty) However I think that it's likely that COM objects don't inherit from the Object class — or at least the same Object class. Or at least don't implement whatever interface is required to do for ... in on them.
Update: While I feel it is irrelevant for the question to be answered — I expect some sort of web client to request (via http) the JSON representation of this object or a collection of this object.
tl;dr The question: What should I do to make it so that the Scripting.Dictionary can be output properly as JSON instead of failing and returning just an empty string? Do I need to 'reinvent the wheel' and write my own Dictionary class in VBScript that does act as a normal object in ASP?
Javascript’s for...in construct (which is used in the JSON serializer you refer to) only works on native JS objects. To enumerate a Scripting.Dictionary’s keys, you need to use an Enumerator object, which will enumerate the keys of the Dictionary.
Now the JSON.stringify method has a nifty way of allowing custom serialization, by checking for the presence of a toJSON method on each property. Unfortunately, you can’t tack new methods on existing COM objects the way you can on native JS objects, so that’s a no-go.
Then there’s the custom stringifier function that can be passed as second argument to the stringify method call. That function will be called for each object that needs to be stringified, even for each nested object. I think that could be used here.
One problem is that (AFAIK) JScript is unable to differentiate VBScript types on its own. To JScript, any COM or VBScript object has typeof === 'object'. The only way I know of getting that information across, is defining a VBS function that will return the type name.
Since the execution order for classic ASP files is as follows:
<script> blocks with non-default script languages (in your case, JScript)
<script> blocks with the default script language (in your case, VBScript)
<% ... %> blocks, using the default script language (in your case, VBScript)
The following could work — but only when the JSON.stringify call is done within <% ... %> brackets, since that’s the only time when both JScript and VBScript <script> sections would both have been parsed and executed.
The final function call would be this:
<%
Response.Write JSON.stringify(oProd, vbsStringifier)
%>
In order to allow JScript to check the type of a COM object, we'd define a VBSTypeName function:
<script language="VBScript" runat="server">
Function VBSTypeName(Obj)
VBSTypeName = TypeName(Obj)
End Function
</script>
And here we have the full implementation of the vbsStringifier that is passed along as second parameter to JSON.stringify:
<script language="JScript" runat="server">
function vbsStringifier(holder, key, value) {
if (VBSTypeName(value) === 'Dictionary') {
var result = '{';
for(var enr = new Enumerator(value); !enr.atEnd(); enr.moveNext()) {
key = enr.item();
result += '"' + key + '": ' + JSON.stringify(value.Item(key));
}
result += '}';
return result;
} else {
// return the value to let it be processed in the usual way
return value;
}
}
</script>
Of course, switching back and forth between scripting engines isn’t very efficient (i.e. calling a VBS function from JS and vice versa), so you probably want to try to keep that to a minimum.
Also note that I haven’t been able to test this, since I no longer have IIS on my machine. The basic principle should work, I’m not 100% certain of the possibility to pass a JScript function reference from VBScript. You might have to write a small custom wrapper function for the JSON.stringify call in JScript:
<script runat="server" language="JScript">
function JSONStringify(object) {
return JSON.stringify(object, vbsStringifier);
}
</script>
after which you can simply adjust the VBScript call:
<%
Response.Write JSONStringify(oProd)
%>
I ended up writing a function to serialize the Dictionary type myself. Unfortunately, you'll have to go in and do a find & replace on any failed dictionary serializations. ({}) I haven't the time to figure out an automated way to do this. You're welcome to fork it on BitBucket.
Function stringifyDictionary( key, value, sp )
dim str, val
Select Case TypeName( sp )
Case "String"
sp = vbCrLf & sp
Case "Integer"
sp = vbCrLf & Space(sp)
Case Else
sp = ""
End Select
If TypeName( value ) = "Dictionary" Then
str = """" & key & """:{" & sp
For Each k in value
val = value.Item(k)
If Not Right(str, 1+len(sp)) = "{" & sp And Not Right(str, 1+len(sp)) = "," & sp Then
str = str & "," & sp
End If
str = str & """" & k & """: "
If TypeName( val ) = "String" Then
If val = "" Then
str = str & "null"
Else
str = str & """" & escapeJSONString( val ) & """"
End If
Else
str = str & CStr( val )
End If
Next
str = str & sp & "}"
stringifyDictionary = str
Else
stringifyDictionary = value
End If
End Function
Function escapeJSONString( str )
escapeJSONString = replace(replace(str, "\", "\\"), """", "\""")
End Function
This was written as a function to use with JSON.stringify's replace argument (2nd arg). However you cannot pass a VBScript function as an argument. (From my experience) If you were to rewrite this function in JScript you could use it when you're calling JSON.stringify to ensure that Dictionaries do get rendered properly. See the readme on BitBucket for more on that. Here's how I implemented it:
dim spaces: spaces = 2
dim prodJSON: prodJSON = JSON.stringify( oProduct, Nothing, spaces)
prodJSON = replace(prodJSON, """Specs"": {}", stringifyDictionary("Specs", oProduct.Specs, spaces * 2))
Known issues:
The closing } for the Dictionary serialization will be the same number of indentations as the properties it contains. I didn't have time to figure out how to deal with that without adding another argument which I don't want to do.
JSON does not inherently encode any type information at all. What JSON enables you to represent is an arbitrary data structure involving either an object or an array of values. Any such object may have an arbitrary number of named properties such that the names are strings and the values are either the constant null, the constants true or false, numbers, strings, objects, or arrays of values.
How such a data structure is realized in any given programming language runtime is your problem :-) For example, when de-serializing JSON into Java, one might use ArrayList instances for arrays, HashMap instances for objects, and native types for simpler values. However, it might be that you really want the objects to be some particular type of Java bean class. To do that, the JSON parser involved would have to be somehow guided as to what sort of objects to instantiate. Exactly how that works depends on the JSON parser and its APIs.
(edit — when I said "no type information at all", I meant for "object" values; clearly booleans, strings, and numbers have obvious types.)
Related
I discovered Javascript ES6 Template Literals today. Just one word: Awesome!
Question: How to store and load Template Literals as JSON? I load some files via XHR, followed by some JSON.parse() which doesn't support ` instead of ", so it seems one can't save Template Literals directly in the files.
Goal: To use this for dynamic strings and translation and to get rid of confusing stuff like ("Hello " + username + "! How are you?") which requires multiple strings to be stored for just one message, and instead save my stuff beautifully and simple as
`Hello, ${username}! How are you?`
where username points to the dynamic variable with the same name. Is that possible? If yes, how to achieve this? It's okay if i have to use a function to somehow convert the strings into Template Literals as long as it doesn't hit hard on the overall performance, but I would like to at least avoid eval.
You can create your own function to parse template literal,
function stringTemplateParser(expression, valueObj) {
const templateMatcher = /{{\s?([^{}\s]*)\s?}}/g;
let text = expression.replace(templateMatcher, (substring, value, index) => {
value = valueObj[value];
return value;
});
return text
}
console.log(stringTemplateParser('my name is {{name}} and age is {{age}}', {name: 'Tom', age:100}));
// output 'my name is Tom and age is 100'
You could always use JSON.stringify to enclose dynamic data:
const data = 'some value';
JSON.stringify({
data,
});
// expected: "{\"data\": \"some value\"}"
I found it easier to separate the problem in a few substrings of JSON. Create the key "message" and this key stores parts of the message. It also works well for i18n.
{
"message" : {
"_0": "first part ",
"_1": "after first variable. ",
"_2": "after another variable"
}
}
And then, after decoding it, you can access it like
${message._0}${variable}${message._1}${var2}${message._2}
Try json-templates. Looks like exactly what you're looking for.
I need to construct and populate a json object with values coming from a method.
A bit of background to this: I'm searching pdf documents with a designated keyword and if I find any match, for each match I need to save:
-the whole sentence where the match is found
-the search term (defined elsewhere: the search term is always the same, so it's really redundant here, but I might need it in the json object that's why I'm including it)
-the result (which is the index where the search term is found in a whole sentence and it should be an integer)
So, here is some code.
I have this function call inside a loop (the loops goes through the pages and then there is a second loop that goes through the text):
for(var i = 0; i < items.length; i++){
lineWithResult = searchPdf(block.str);
if(lineWithResult != null){
console.log(lineWithResult + " wordCounter is " + wordCounter);
}
}
and the function itself:
function searchPdf(toSearch){
var result = toSearch.toLowerCase().indexOf(searchTerm);
if(result >=0){//if match is found
wordCounter++;
//console.log("toSearch " + toSearch + " result is " + result + " wordCounter " + wordCounter);
return toSearch;
}
else{//if match not found
return null;
}
}
SO I need to construct a json object that at each iteration takes in the parameters discussed above:
So, what would be the best way - I'm a bit rusty with json?
I think I would start by creating an empty object like so (if that's even a valid definition):
var searchResult = {"Line" : "", "SearchTerm" : "", "Result" : ""}
If the above is right, where do I define the object and how do I fill it up with the relevant values? Bear in mind that there will be a lot of Lines, one search term and a lot of Results because the documents (a pdf) which I will use are quite big and can returns lots of matches
thanks
With saying something like that:
var searchResult = {"Line" : "", "SearchTerm" : "", "Result" : ""}
You have already defined the object. JavaScript (at this point) is prototypical, not a "class" based language. JSON in JavaScript is not much more than just a plain JavaScript object. If you want to to create multiple objects of that kind, you have various options. I recommend you to read about JS Object creational patterns.
Here is a good link.
That being said, you could do something like that:
// ... maybe inside a function
return {
line: myLineValue,
searchTerm: mySearchtermValue,
result: myResult
}
There is no need to init something with empty values; you just create the object with the curly brackets.
Hope this makes sense to you; if not, let me know in the comments, and I will try to improve my answer. :-)
I have some data in the form
[
{
"name": "alex",
"fullname": "Alessandro Magno"
},
{
"name": "alex",
"fullname": "Alessandro Magno"
}
]
but (name and fullname) are not fixed, they may be "key"/"value", or whatever.
I'm also given a template to follow, e.g.:
var template = "<span>data.fullname (data.name)</span>".
My problem is the following:
I have to look for all the "data." in the string,
obtain the value following it (in this case, fullname and name),
and replace data. with my external source. E.g.
str.replace("data.<value>", source[value]);
In Javascript.
Thanks!
EDIT: I probably explained it wrongly. My source is defined (in this case, a name/fullname list in JSON format). I also have a template to follow. I have to replace the syntax used in the template (which is "data." ['data.' is fixed]) with the correspondent value of the source. Hope this is clearer now!
Re-thanks!
A simple replace call using a string will only replace a single instance of a substring. You'll have to use a regular expression.
Looking at what you're trying to do, I'd say you're probably looking for something like:
var s = "<span>data.fullname (data.name)</span>";
var replacements = {name: 'alex', fullname: 'alessandro'};//example
s.replace(/(data\.)([a-z]+)/g, function(a,b,c)
{
return b + (repl[c] || 'none');
});
This results in "data.alessandro (data.alex)"
How it works? Central to this approach is the regular expression:
/(data\.)([a-z]+)/g, which is quite basic:
(data\.): match and capture the literal string "data."
([a-z]+): again: match & capture 1 or more chars fater data. -> "data."
g: is the global flag, apply this patter to the entire string.
Now, for each match for this pattern that is found, instead of providing a replacement string, I provide a function, that is passed the matched substring (and the captured groups as separate arguments), and use the function construct a replacement string:
function(a, b, c)
{//a -> entire substring, b-> data., c-> string after data.
return b + (replacements[c] || 'none');// logical || to provide default string replacement
}
It's as simple as that, really. Given that your values are contained by objects, that are in an array, you could opt to code the following:
var vals = [{name: 'alex', fullname: 'alessandro'},{name: 'alex2', fullname: 'alessandro2'}],
results =[],
template = "<span>data.fullname (data.name)</span>";
for (var i=0;i<vals.length;++i)
results[i] = template.replace(/(data\.)([a-z]+)/g, function(a,b,c)
{
return b + (vals[i][c] || 'default');
});
Edit:
To remove data. substring, too, change the callback function (and pattern) to:
s.replace(/data\.([a-z]+)/function(a,c)
{
return replacements[c] || 'none';
});
In case of the code above:
for (var i=0;i<vals.length;++i)
results[i] = template.replace(/data\.([a-z]+)/g, function(a,c)
{
return vals[i][c] || 'default';
});
Is what you're after
If you need to use a template you could use template engine. Basically it will allow you to bind an object to a template. Behind the scene the template is parsed using regex. Pseudo-code:
var template = "<span>{fullname} ({name})</span>";
var html = template({ fullname: data.fullname, name: data.name });
To name a few:
http://handlebarsjs.com/
http://mustache.github.io/
http://underscorejs.org/
Underscore is more a library tool belt, but it provides a simple templating engine that works well for common use.
It is not clear what the transformation you are trying to achieve is, but I can tell you that you should not be using regex to achieve it! Transform your JSON into JavaScript objects using JSON.parse, transform the objects directly, then convert back to JSON via JSON.stringify.
I'd like a JSON pretty printer that would recognize when an array or object fits on one line and just do that. Example:
{
"fits": ["JSON", "pretty", "printer"],
"longer": [
"???????????????????????????????????????????????????",
"???????????????????????????????????????????????????",
"???????????????????????????????????????????????????",
"???????????????????????????????????????????????????",
"???????????????????????????????????????????????????"
]
}
Is there a standalone library like this? If not, how would I go about writing one?
I'm most interested in a JavaScript implementation.
I don't know about any such concise JSON printer, but it shouldn't be hard to make your own if you want to:
You can use the for(property in object) to iterate over the properties of a given object.
Depending on use case, you might want to filter with hasOwnProperty.
You can determine if an reference points to an object, array, string or number with typeof
Have your pretty printer function receive the initial indentation offset and the object to be printed. This might be enough to decide if you should inline each property or not.
I'm not sure this "greedy" strategy is always "optimal" - perhaps it might be better to do something in multiple lines now to be able to inline later. I wouldn't worry with this at first though.
Since JSON is primarily a data transport format, I assume that you mean viewing raw JSON in the browser? If so, then there are a few options:
JSON Lint - Checks and reformats JSON
A pure JS version of the above
JSONView for Chrome
Safari JSON Formatter
You should be able to dig into the source of the last three if you require further customization. I'd start with iterating through the value.length property where value is/are the array element(s) to see if you can limit your output to a single line.
Use a replacer function to compare the total number of characters in each key/value pair to a fixed length. Here is a simple example:
function replacer(key, value)
{
var key_arr = [];
var value_arr = [];
var i = 0;
for (_ in value)
{
key_arr.push(_);
value_arr.push(value[_]);
}
for(;i < value_arr.length;i++)
{
if (key_arr[i].length + value_arr[i].length < 80)
{
console.log(key_arr[i] + ":" + "\t" + value_arr[i])
}
else
{
console.log(key_arr[i] + ":" + "\n" + value_arr[i])
}
}
}
Usage:
var json;
json = {"foo":"1","bar":"2"},
JSON.stringify(json, replacer, 4);
json = {"foo":"12345678901234567890123456789012345678901234567890123456789012345678901234567890","bar":"2"};
JSON.stringify(json, replacer, 4);
i have i string like
5: "White", 6: "Yellow", 7: "Pink"
i need that string view like this
s={5: "White", 6: "Yellow", 7: "Pink"};
for attach it to select on form
for (var a in myOpts)
{
var t = document.createElement("OPTION");
t.value = a;
t.appendChild(document.createTextNode(myOpts[a]));
selectObj.appendChild(t);
}
If you json_decode your string s, you will get a plain object with 3 owned properties. Then you can loop on those properties with the for..in construct:
var myOpts, s='{5: "White", 6: "Yellow", 7: "Pink"}';
eval('myOpts='+s); // or better, a json parser
for(var a in myOpts) {
if(myOpts.hasOwnProperty(a)) {
// your dom code here
}
}
Your question is a little unclear, however - I'll try to guess what you're saying, cover all cases and give a little added value ;)
I assume that you have a server-side string in a JSP page, who's value is
5: "White", 6: "Yellow", 7: "Pink"
What makes it look like
<%
String data = "5: \"White\", 6: \"Yellow\", 7: \"Pink\"";
%>
And now you want to write it to to the document, so you can for it later on on client-side code.
In that sense - you need to distinguish between few cases.
Although when the server writes it into the response document it is a string - the client code can get this value in different ways. In all of them - the server must write the data in a specific way so that the client can access it, however, the validity rules are different.
Write an object literal
Actually - I think that that's what you're trying to do.
The client code does not get it as a string - but as an object literal.
<script>
var sObjData = {<%= safeJs(data) %>};
</script>
The limits of this choice is that whatever comes from the server code (within the <%%>) and whatever comes from the markup (outside the <%%>) must work together as a valid JavaScript object literal.
There are many things that can break legality of this Object literal - like broken strings, missing commas, missing colons, and so on. Although this is the recommended way - you have to know what you're doing, and I advise you to gap up this knowledge, and your example is a good start.
In your example - this renders to a valid JavaScript Object-Literal, and the problem is not there.
<script>
var sObjData = {5: "White", 6: "Yellow", 7: "Pink"};
</script>
This is a perfectly legal object literal that can be used in for - just the way you do.
It could be that your example is simplification of your case, and the strings that you use may break your execution. Here's how to handle strings from server to client-code:
Write a JavaString string
The limits in this case - is that any character in data string that might break JavaSctipt strinbngs - must be escaped for javascript.
Here I just treat the whole data value, however - bear in mind that you might want to do the same for every values that you put inside this data.
Here's the simplest implementation that explains that's to escape a Java string for JavaScript:
<%!
String safeJs(String data){
return data.replace("\"","\\\"") //why three? two emit a sigle \ and the third escape the "
.replace("'","\\'")
.replace("\n","\\n") //why two? you're not escaping n, your're emitting \ and n
.replace("\r","\\r"); // that will render as escaping for the client code
}
%>
<script>
var sObjData = '<%= safeJs(data) %>';
<script>
This part will assure that you get all the data from the server to the client, and that it will be accessible to the client. From there - it's a matter of your own protocol of delivering data and parsing it on the client.
However, this is not always recommended: If all you're delivering can be formulated as an Object Literal - it is much better - because the Browser handles the parsing for you in a complied code, and gives the scripting code a ready-made object.
Unless you want to parse your own string-protocol, seemingly - its gets the same result to the following, so why bother? better to safeJs your values.
<script>
var sObjData = '<%= safeJs(data) %>';
var oObjData = eval("{" + oObjData + "}");
<script>
Write the string to a content of a TextArea
This is the most robust way of passing strings between server and an HTML client - because the only thing that can break - is if the data string contains a closing tag of TextArea.
A text area is immune to line-breaks, it is immute to quotation marks (single and double), it's sole weakness is it's own closing tag.
Note that replacing the "" with "<textarea>" and "" with "<textarea>".
Assigning an ID to the text area and putting it in style="display:none" assures that it will not bother the UI, and yet be accessible.
<textarea style="display:none" id="txtData"><%=data.replace("</","</")%></textarea>
<script>
var s = document.getElementById("txtData");
</script>
building options in a Select
The DHTML tricks of createElement works, however, I rarely use it, because it's cumbersome, and very low on performance.
However - if you managed to write your Object Literal properly - it should work.
injecting HTML
Instead of creating a select and trying to populate it - it is faster and more reliable to inject it completely into the DOM.
I use the following utility for that:
function getStringBuffer(){
var bfr = [];
bfr.add = function() {
for(var i=0;i<arguments.length;i++) {
this[this.length] = arguments[i];
}
}
bfr.toString = function() { return this.join("") }
return bfr;
}
The wrap in (function(){ and })() creates an anonymous function and executes it instantly - that assures that no variables that are declared for this work will pollute the global scope.
first way - using document.write:
<script>
(function(){
var HTML = getStringBuffer();
var k;
HTML.add("<select id='selectObj'>");
for (k in myOpts) {
HTML.add("<option value='", k, "'>", myOpts[k],"</option>");
}
HTML[HTML.length] = "</select>";
document.write(HTML); //note the overriden toString method that will be called here
})();
</script>
Second way - using innerHTML
You can do the same and instead document.write - use a container tag to mark the place of the select, and inject it there even after the DOM has finished loading.
it's the same as the first way, in one difference: Instead document.write(HTML); -
put a container, say <span id="oSelectPlace"></span> where you want the select to be, and then use
document.getElementById("oSelectPlace").innerHTML = HMTL;