version 11 clear mata mata: /* class: libjson * Purpose: To parse JSON strings into a object tree, and to facilitate common web API calls which give JSON responses * Author: Erik Lindsley * Date: January 28th, 2012 * Version: 1.0.3 * Change Log: * 20140119: Fixed space insertion issue when source has no end-of-lines. * Known Limitations: STATA/Mata does not currently support encrypted transport (https://) URLs. * The parser should really be fully static, but STATA/Mata doesn't support accessing static class variables from inside static functions. */ class libjson { /* NOTE: JSON = Java Script Object Notation, libjson = STATA/MATA libjson class */ private: real scalar mytype /* node Run-Time Type Identification (RTTI) field */ string scalar pname /* Holds the key(label) for Artibute nodes */ pointer (class libjson scalar) colvector vals /* Arry for holding json objects, either for Array or Objects */ string scalar pval /* Holds string values for Scalar objects */ pointer (class libjson scalar) scalar pval_c /* Holds libjson objects for Attribute objects */ void addEntry() /* Adds the given libjson object to the end of the array */ static real rowvector s /* Parse buffer */ static real scalar idx /* current character being processed by the parser*/ static real scalar last_error /* Last parser error */ static string scalar quote /* quote character to be use by the pretty printer*/ static string scalar vstop /* Used buy the pretty printer to keep track of the current indention level */ public: void new() /* Constructor */ real scalar arrayLength() /* returns the number of objects in an Array */ pointer (class libjson scalar) scalar getArrayValue() /* returns Nth array element */ real scalar isArray() /* true if this libjson object is holding an Array? */ real scalar isObject() /* true if this libjson object is holding an Object? */ real scalar isAttribute() /* true if this libjson object is holding an Attribute (name/value pair)? */ real scalar isString() /* true if this libjson object is holding a string literal, or an attribute what holds a literal)? */ real scalar isScalar() /* true if this libjson object is holding a scalar value (=string)? */ string scalar getAttributeScalar() /* returns the named attribute as a scalar (string) value */ pointer (class libjson scalar) scalar getAttribute() /* returns a pointer to the named attribute contained in this Object */ string scalar getAttributeName() /* returns the name of an Attribute */ void addArrayValue() /* Adds the libjson object to the end of the array */ void addArrayScalar() /* Adds the string to the end of the array as a Scalar object*/ void addAttributeScalar() /* Adds the key/value(string) to the end of the array as a Scalar Attribute object*/ void addAttribute() /* Adds the libjson object to the end of the array as a Scalar Attribute object*/ void makeScalar() /* Force the libjson object to hold the given string */ string rowvector listAttributeNames() /* Returns a list of the attributes of the Object */ string scalar bracketArrayScalarValues() /* Returns all scalar values in the array in the familiar bracket notation */ string matrix flattenToKV() /* Return the entire libjson object as a list of flattened object names and values. Useful for processing small libjson responses from web servers. */ static string scalar getFlattenedValue() /*Scans the given flatten() resutls for the given key, and returns the scalar (string) value. A helper function for scanning the result of flatten() for particular key-value pairs */ static string scalar urlencode() /* Returns the given string with certain unsafe characters escaped as necessary for use in a URL */ static string scalar getrawcontents() /* Makes an http request using the given URL and extra arguments, and returns the results as a single string */ pointer (class libjson scalar) scalar parse() /* parses the given JSON-formatted string into a libjson object tree, and returns the root node */ void prettyPrint() /* Print to the console the libjson object tree in a human-readible form that is JSON compliant */ string scalar toString() /* Reconstitutes the given libjson tree into a JSON string. Note that this will not be a perfect reproduction of the original input becuase all numbers and 'null' values get converted to strings */ /* Tree selector helper functions */ pointer (class libjson scalar) scalar getNode() /* Returns the node branch address by given selector */ string scalar getString() /* Returns the Scalar addressed by given selector */ real scalar getReal() /* Returns the real number (converted from the Scalar addressed by given selector) */ static string rowvector parseSelector() /* converts a selector string into the vector format by breaking up the string at the colons */ /* used for calling JSON-based REST APIs */ static pointer (class libjson scalar) scalar webcall() /* Calls the REST API using the given URL and Args, parses the JSON response, and returns the root node of the libjson object tree */ static string matrix webcall_flatten() /* Call the API, and pass the results though flatten() */ static real rowvector getVersion() /* Returns a vector with the library version number, in form of (major, minor, build) */ static real scalar checkVersion() /* true if the library version is the same or bettr than the given version */ private: string colvector pretty_print() /* private helper function for prettyPrint() */ string matrix flatten() /* Return the entire libjson object as a list of flattened object names and values. Useful for processing small libjson responses from web servers. */ /* JSON top-down, recursive parser methods */ string scalar parseQuotedString() /* parse and return the quoted string */ void skipws() /* skip white space characters */ string scalar parseAtom() /* parse a string of numeric characters, stoping at a whitespace or other reserved character */ pointer (class libjson scalar) scalar parseObject() /* Parse a JSON Object, returning a libjson node */ pointer (class libjson scalar) scalar parseArray() /* Parse a JSON Array, returning a libjson node */ } real rowvector libjson::getVersion() { return (1,0,2); } real scalar libjson::checkVersion(real rowvector c) { v=getVersion(); return( ((v[1]==c[1]) && (v[2]>=c[2])) || (v[1]>c[1]) ); } string matrix libjson::flattenToKV() { return(flatten(""));} string rowvector libjson::parseSelector(string scalar sel) { tok = tokens(sel,":") NC = ceil(cols(tok)/2) res=J(1,NC,""); for (kk=1; kk<=NC; kk++) res[kk]=tok[kk*2-1]; return(res); } string scalar libjson::bracketArrayScalarValues() { if (!isArray()) return(""); string scalar res res="" for (k=rows(vals); k>=1; k--) { if (vals[k]->isString()) res = vals[k]->pval+", "+ res; } return("["+ substr(res,1,strlen(res)-2) + "]"); } string rowvector libjson::listAttributeNames(real scalar one_string_flag) { if (!isObject()) return(("")); string scalar quote quote = char(34); if (one_string_flag) { string scalar res res="" for (k=rows(vals); k>=1; k--) { res = quote+(vals[k]->getAttributeName())+quote +" "+ res; } return((res)); } else { string rowvector res2 res2 = J(1,rows(vals), "") for (k=rows(vals); k>=1; k--) { res2[k]=vals[k]->getAttributeName(); } return(res2); } } pointer (class libjson scalar) scalar libjson::webcall(string scalar url_base, string matrix args) { class libjson scalar res c = getrawcontents(url_base,args); if (strlen(c)>0) return(res.parse(c)); return(NULL); } string matrix libjson::webcall_flatten(string scalar url_base, string matrix args) { pointer (class libjson scalar) scalar root root = webcall(url_base,args); if (root) return(root->flatten("")); else return(J(0,0,"")); } pointer (class libjson scalar) scalar libjson::getNode(string rowvector selector) { pointer (class libjson scalar) scalar nod NC=cols(selector) nod=&this if ((selector==.) || (selector=="") ) return(nod) for (k=1; k<=NC; k++) { if (nod->mytype == 4) { array_idx = strtoreal(selector[k]); if ((array_idx<1) || (array_idx>rows(nod->vals)) ) return(NULL); nod=nod->vals[array_idx]; } else if ( nod->mytype == 3) { key=selector[k]; match=0; for (r=rows(nod->vals); r>=1; r--) { if ((nod->vals[r])->pname == key) { match=1; if ((nod->vals[r])->mytype==2) { nod=nod->vals[r]; } else nod=(nod->vals[r])->pval_c; break; } } if (match==0) return(NULL); } else { return(NULL); } } return(nod); } string scalar libjson::getString(string rowvector selector, string scalar missing_val) { pointer (class libjson scalar) scalar nod nod = getNode(selector); if (nod) { if (nod->isString()) return(nod->pval); } return(missing_val); } real scalar libjson::getReal(string rowvector selector, real scalar missing_val) { pointer (class libjson scalar) scalar nod nod = getNode(selector); if (nod) { if (nod->isString()) { return (strtoreal(nod->pval)); } } return(missing_val); } void libjson::addAttributeScalar(string scalar key, string scalar val) { class libjson scalar a a.pname=key a.mytype=2 a.pval=val addEntry(&a); mytype=3; } void libjson::addAttribute(string scalar key, pointer (class libjson scalar) val) { class libjson scalar a a.pname=key a.mytype=2.5 a.pval_c=val addEntry(&a); mytype=3; } pointer (class libjson scalar) scalar libjson::getAttribute(string scalar key) { if (mytype==1) return(pval_c) if (mytype==3) { for (k=rows(vals); k>=1; k--) { if (vals[k]->pname == key) { if (vals[k]->mytype==2.5) return(vals[k]->pval_c) else return(vals[k]); } } } return(NULL) } string scalar libjson::getAttributeScalar(string scalar key, string scalar missing_value) { if (mytype==1) return(pval) if (mytype==3) { for (k=rows(vals); k>=1; k--) { if (vals[k]->pname == key && vals[k]->mytype==2) return(vals[k]->pval) } } return(missing_value) } string scalar libjson::getAttributeName() { return(pname); } void libjson::new() { mytype = 0 } real scalar libjson::isArray() {return((&this) && mytype ==4); } real scalar libjson::isObject() {return((&this) && mytype ==3); } real scalar libjson::isAttribute() {return((&this) && ((mytype ==2)||(mytype ==2.5))); } real scalar libjson::isScalar() {return((&this) && mytype==1); } real scalar libjson::isString() {return((&this) && ((mytype==1)||(mytype==2)) ); } void libjson::makeScalar( string scalar val) { mytype=1; pval = val; } void libjson::addEntry(pointer (class libjson scalar) scalar p) { if (mytype==0) vals = J(0,1,NULL) vals = vals \ p; } void libjson::addArrayScalar(string scalar s) { class libjson scalar temp temp.makeScalar(s) addEntry(&temp) mytype=4; } void libjson::addArrayValue(pointer (class libjson scalar) scalar p) { addEntry(p) mytype=4; } real scalar libjson::arrayLength() { if (mytype==4) return(rows(vals)) else return(0); } pointer (class libjson scalar) scalar libjson::getArrayValue(real scalar index) { if ((mytype==4) && (index<= rows(vals)) && (index>=1) ) return(vals[index]) else return(NULL) } string matrix libjson::flatten(string scalar prefix) { string matrix res res = J(0,2,"") if (mytype==1) { res = res \ (substr(prefix,2,.), pval) } else if (mytype==2) { if (prefix!="") res = res \ (substr(prefix,2,.) + ":"+ pname, pval) else res = res \ (pname, pval) } else if (mytype==2.5) { res = res \ (pval_c->flatten(prefix+":"+ pname) ) } else if (mytype==3) { for (k=1; k<=rows(vals); k++) { res = res \ (vals[k]->flatten(prefix)) } } else if (mytype==4) { for (k=1; k<=rows(vals); k++) { res = res \ (vals[k]->flatten(prefix+":"+strofreal(k)) ) } } return(res) } void libjson::prettyPrint() { quote=char(34) vstop = " " pp=pretty_print(""); if (orgtype(pp)=="scalar") { printf("%s\n",pp); } else { for (k=1; kpretty_print(lmargin+ vstop)) ); } else if (mytype==3) { NR=rows(vals); res = res \ (lmargin + "{"); for(k=1; k<=NR; k++) { res = res \ (vals[k]->pretty_print(lmargin+ vstop)); lastrow = rows(res); if (kpretty_print(lmargin+ vstop); lastrow = rows(res); if (k=44 && a[c]<=59) || (a[c]>=64 && a[c]<=122)) { res=(res,a[c]);} else { h1 = floor(a[c]/16); h2 = mod(a[c],16); if (h1<10) {h1=h1+48;} else {h1=h1+55;} if (h2<10) {h2=h2+48;} else {h2=h2+55;} res=(res, 37, h1,h2);} } return(char(res));} void libjson::skipws() { while(s[idx]<=32) idx++; } string scalar libjson::parseQuotedString() { if (s[idx]!=34) return(""); idx++; start_idx = idx; while( (s[idx]!=34) || (s[idx-1]==92) ) idx++; idx++; return (char(s[(start_idx)..(idx-2)])); } string scalar libjson::parseAtom() {start_idx = idx; while(((s[idx]>=48) && (s[idx]<=57)) || (s[idx]==45) || (s[idx]==43)|| (s[idx]==46)|| (s[idx]==69)|| ((s[idx]>=97)&& (s[idx]<=122))) idx++; return (char(s[start_idx..(idx-1)])); } string scalar libjson::getrawcontents(string scalar url_base, string matrix args) { a = ""; if ((args!=.) && ((rows(args)>0) && (cols(args)==2)) ) { a = urlencode(args[1,1])+"="+urlencode(args[1,2]); for (r=2; r<=rows(args); r++) { a= a+"&"+ urlencode(args[r,1])+"="+urlencode(args[r,2]); } url = url_base + "?"+ a; } else url= url_base; res = ""; fh = _fopen(url, "r"); if (fh!=0) { printf ("{err: Fatal Error %f, unable to open URL: %s}\n",fh,ferrortext(fh)); return (res); } while ((line=fread(fh,4000))!=J(0,0,"")) { res=res+line; } /* changed 20140119,20140929 */ fclose(fh); return(res); } pointer (class libjson scalar) scalar libjson::parse(string scalar libjson_string) { pointer (class libjson scalar) scalar res if (strlen(libjson_string)<3) return(NULL); s=ascii(libjson_string) /* printf("DEBUG: libjson::parse(%f,%f)\n",strlen(libjson_string), cols(s)); */ idx=1 last_error=0 skipws(); if (s[idx]==123) res=parseObject(); else res=parseArray(); if (res && (last_error==0)) return(res); printf("libjson::parse: unexpected character '%s' at position %f forced error #%f\n",char(s[idx]),idx, last_error); printf("%s\n", substr(char(s[(idx-30)..(cols(s))]),1,60)); printf(" ^ parse error here!\n"); char(s[(idx-100)..(idx+29)]) return(NULL) } pointer (class libjson scalar) scalar libjson::parseObject() { class libjson scalar res /* printf("DEBUG: libjson:: BEGIN parseObject(%f,%f)\n",cols(s),idx); */ skipws(); if (s[idx]!=123) { last_error = -1; return(NULL); } idx++; skipws(); if (s[idx]==125) return(&res); while (1) { skipws(); k=parseQuotedString(); skipws(); if (k==. || s[idx]!=58) { last_error = -3; return(NULL); } idx++; skipws(); if (s[idx]==34) { v = parseQuotedString(); res.addAttributeScalar(k,v);} else if (s[idx]==123) {v= parseObject(); if (v) res.addAttribute(k,v); else return(NULL); } else if (s[idx]==91) { v= parseArray();if (v) res.addAttribute(k,v); else return(NULL); } else if ( ((s[idx]>=48) && (s[idx]<=57)) || (s[idx]==45) ||((s[idx]>=97)&& (s[idx]<=122)) ) { v= parseAtom(); res.addAttributeScalar(k,v); } else { last_error = -4; return(NULL); } skipws(); if (s[idx]==125) break; if (s[idx]!=44) { last_error=-22; return(NULL); } idx++; } if (idx=48) && (s[idx]<=57)) || (s[idx]==45) ||((s[idx]>=97)&& (s[idx]<=122)) ) { v= parseAtom(); res.addArrayScalar(v);} else if (s[idx]==93) break; else { last_error = -11; return(NULL); } skipws(); if (s[idx]==93) break; if (s[idx]!=44) { last_error = -12; return(NULL); } idx++; } if (idx=1; r--) { if (flattened_results[r,1]==key) return (flattened_results[r,2]); } return (value_if_missing); } mata mlib create libjson, replace mata mlib add libjson *(), complete end