jsonhelpers.hpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2014 HPCC Systems®.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. // jsonhelpers.hpp:
  14. //
  15. //////////////////////////////////////////////////////////////////////
  16. #pragma warning( disable : 4786)
  17. #ifndef _JSONHELPERS_HPP__
  18. #define _JSONHELPERS_HPP__
  19. #include "jliball.hpp"
  20. #include "wsexcept.hpp"
  21. #define REQSF_ROOT 0x0001
  22. #define REQSF_SAMPLE_DATA 0x0002
  23. #define REQSF_TRIM 0x0004
  24. #define REQSF_ESCAPEFORMATTERS 0x0008
  25. #define REQSF_FORMAT 0x0010
  26. #define REQSF_EXCLUSIVE (REQSF_SAMPLE_DATA | REQSF_TRIM)
  27. namespace JsonHelpers
  28. {
  29. static StringBuffer &appendJSONExceptionItem(StringBuffer &s, int code, const char *msg, const char *objname="Exceptions", const char *arrayName = "Exception")
  30. {
  31. if (objname && *objname)
  32. appendJSONName(s, objname).append('{');
  33. if (arrayName && *arrayName)
  34. appendJSONName(s, arrayName).append('[');
  35. delimitJSON(s);
  36. s.append('{');
  37. appendJSONValue(s, "Code", code);
  38. appendJSONValue(s, "Message", msg);
  39. s.append('}');
  40. if (arrayName && *arrayName)
  41. s.append(']');
  42. if (objname && *objname)
  43. s.append('}');
  44. return s;
  45. }
  46. static StringBuffer &appendJSONException(StringBuffer &s, IException *e, const char *objname="Exceptions", const char *arrayName = "Exception")
  47. {
  48. if (!e)
  49. return s;
  50. StringBuffer temp;
  51. return appendJSONExceptionItem(s, e->errorCode(), e->errorMessage(temp).str(), objname, arrayName);
  52. }
  53. static StringBuffer &appendJSONException(StringBuffer &s, IWsException *e, const char *objname="Exceptions", const char *arrayName = "Exception")
  54. {
  55. if (!e)
  56. return s;
  57. StringBuffer temp;
  58. IArrayOf<IException>& exceptions = e->getArray();
  59. for (unsigned i = 0 ; i < exceptions.ordinality(); i++)
  60. {
  61. appendJSONExceptionItem(s, e->errorCode(), e->errorMessage(temp.clear()).str(), objname, arrayName);
  62. }
  63. return s;
  64. }
  65. static StringBuffer &appendJSONExceptions(StringBuffer &s, IMultiException *e, const char *objname="Exceptions", const char *arrayName = "Exception")
  66. {
  67. if (!e)
  68. return s;
  69. if (objname && *objname)
  70. appendJSONName(s, objname).append('{');
  71. if (arrayName && *arrayName)
  72. appendJSONName(s, arrayName).append('[');
  73. ForEachItemIn(i, *e)
  74. appendJSONException(s, &e->item(i), NULL, NULL);
  75. if (arrayName && *arrayName)
  76. s.append(']');
  77. if (objname && *objname)
  78. s.append('}');
  79. return s;
  80. }
  81. static IException *MakeJSONValueException(int code, const char *start, const char *pos, const char *tail, const char *intro="Invalid json format: ")
  82. {
  83. StringBuffer s(intro);
  84. s.append(pos-start, start).append('^').append(pos);
  85. if (tail && *tail)
  86. s.append(" - ").append(tail);
  87. return MakeStringException(code, "%s", s.str());
  88. }
  89. static inline StringBuffer &jsonNumericNext(StringBuffer &s, const char *&c, bool &allowDecimal, bool &allowExponent, const char *start)
  90. {
  91. if (isdigit(*c))
  92. s.append(*c++);
  93. else if ('.'==*c)
  94. {
  95. if (!allowDecimal || !allowExponent)
  96. throw MakeJSONValueException(-1, start, c, "Unexpected decimal");
  97. allowDecimal=false;
  98. s.append(*c++);
  99. }
  100. else if ('e'==*c || 'E'==*c)
  101. {
  102. if (!allowExponent)
  103. throw MakeJSONValueException(-1, start, c, "Unexpected exponent");
  104. allowDecimal=false;
  105. allowExponent=false;
  106. s.append(*c++);
  107. if ('-'==*c || '+'==*c)
  108. s.append(*c++);
  109. if (!isdigit(*c))
  110. throw MakeJSONValueException(-1, start, c, "Unexpected token");
  111. }
  112. else
  113. throw MakeJSONValueException(-1, start, c, "Unexpected token");
  114. return s;
  115. }
  116. static inline StringBuffer &jsonNumericStart(StringBuffer &s, const char *&c, const char *start)
  117. {
  118. if ('-'==*c)
  119. return jsonNumericStart(s.append(*c++), c, start);
  120. else if ('0'==*c)
  121. {
  122. s.append(*c++);
  123. if (*c && '.'!=*c)
  124. throw MakeJSONValueException(-1, start, c, "Unexpected token");
  125. }
  126. else if (isdigit(*c))
  127. s.append(*c++);
  128. else
  129. throw MakeJSONValueException(-1, start, c, "Unexpected token");
  130. return s;
  131. }
  132. static StringBuffer &appendJSONNumericString(StringBuffer &s, const char *value, bool allowDecimal)
  133. {
  134. if (!value || !*value)
  135. return s.append("null");
  136. bool allowExponent = allowDecimal;
  137. const char *pos = value;
  138. jsonNumericStart(s, pos, value);
  139. while (*pos)
  140. jsonNumericNext(s, pos, allowDecimal, allowExponent, value);
  141. return s;
  142. }
  143. typedef enum _JSONFieldCategory
  144. {
  145. JSONField_String,
  146. JSONField_Integer,
  147. JSONField_Real,
  148. JSONField_Boolean,
  149. JSONField_Present //true or remove
  150. } JSONField_Category;
  151. static JSONField_Category xsdTypeToJSONFieldCategory(const char *xsdtype)
  152. {
  153. //map XML Schema types used in ECL generated schemas to basic JSON formatting types
  154. if (streq(xsdtype, "integer") || streq(xsdtype, "nonNegativeInteger"))
  155. return JSONField_Integer;
  156. if (streq(xsdtype, "boolean"))
  157. return JSONField_Boolean;
  158. if (streq(xsdtype, "double"))
  159. return JSONField_Real;
  160. if (!strncmp(xsdtype, "decimal", 7)) //ecl creates derived types of the form decimal#_#
  161. return JSONField_Real;
  162. if (streq(xsdtype, "none")) //maps to an eml schema element with no type. set to true or don't add
  163. return JSONField_Present;
  164. return JSONField_String;
  165. }
  166. static void buildJsonAppendValue(IXmlType* type, StringBuffer& out, const char* tag, const char *value, unsigned flags)
  167. {
  168. JSONField_Category ct = xsdTypeToJSONFieldCategory(type->queryName());
  169. if (ct==JSONField_Present && (!value || !*value))
  170. return;
  171. if (tag && *tag)
  172. out.appendf("\"%s\": ", tag);
  173. StringBuffer sample;
  174. if ((!value || !*value) && (flags & REQSF_SAMPLE_DATA))
  175. {
  176. type->getSampleValue(sample, NULL);
  177. value = sample.str();
  178. }
  179. if (value)
  180. {
  181. switch (ct)
  182. {
  183. case JSONField_String:
  184. appendJSONValue(out, NULL, value);
  185. break;
  186. case JSONField_Integer:
  187. appendJSONNumericString(out, value, false);
  188. break;
  189. case JSONField_Real:
  190. appendJSONNumericString(out, value, true);
  191. break;
  192. case JSONField_Boolean:
  193. if (strieq(value, "default"))
  194. out.append("null");
  195. else
  196. appendJSONValue(out, NULL, strToBool(value));
  197. break;
  198. case JSONField_Present:
  199. appendJSONValue(out, NULL, true);
  200. break;
  201. }
  202. }
  203. else
  204. out.append("null");
  205. }
  206. inline void checkNewlineJsonMsg(StringBuffer &out, unsigned &level, unsigned flags, int increment)
  207. {
  208. if (!(flags & REQSF_FORMAT))
  209. return;
  210. out.newline();
  211. level+=increment;
  212. if (level>0)
  213. out.pad(level);
  214. }
  215. inline void checkDelimitJsonMsg(StringBuffer &out, unsigned level, unsigned flags)
  216. {
  217. delimitJSON(out, (flags & REQSF_FORMAT));
  218. if (out.length() && out.charAt(out.length()-1)=='\n')
  219. out.pad(level);
  220. }
  221. static void buildJsonMsg(unsigned &level, StringArray& parentTypes, IXmlType* type, StringBuffer& out, const char* tag, IPropertyTree *reqTree, unsigned flags)
  222. {
  223. assertex(type!=NULL);
  224. if (flags & REQSF_ROOT)
  225. {
  226. out.append("{");
  227. checkNewlineJsonMsg(out, level, flags, 2);
  228. }
  229. const char* typeName = type->queryName();
  230. if (type->isComplexType())
  231. {
  232. if (typeName && !parentTypes.appendUniq(typeName))
  233. return; // recursive
  234. int startlen = out.length();
  235. if (tag)
  236. appendJSONName(out, tag);
  237. out.append('{');
  238. checkNewlineJsonMsg(out, level, flags, 2);
  239. int taglen=out.length()+1;
  240. if (type->getSubType()==SubType_Complex_SimpleContent)
  241. {
  242. if (reqTree)
  243. {
  244. const char *attrval = reqTree->queryProp(NULL);
  245. out.appendf("\"%s\" ", (attrval) ? attrval : "");
  246. }
  247. else if (flags & REQSF_SAMPLE_DATA)
  248. {
  249. out.append("\"");
  250. type->queryFieldType(0)->getSampleValue(out,tag);
  251. out.append("\" ");
  252. }
  253. }
  254. else
  255. {
  256. int flds = type->getFieldCount();
  257. for (int idx=0; idx<flds; idx++)
  258. {
  259. checkDelimitJsonMsg(out, level, flags);
  260. IXmlType *childType = type->queryFieldType(idx);
  261. const char *childname = type->queryFieldName(idx);
  262. bool repeats = type->queryFieldRepeats(idx);
  263. if (repeats)
  264. {
  265. Owned<IPropertyTreeIterator> children;
  266. if (reqTree)
  267. children.setown(reqTree->getElements(childname));
  268. appendJSONName(out, childname);
  269. out.append('[');
  270. checkNewlineJsonMsg(out, level, flags, 2);
  271. if (!children)
  272. buildJsonMsg(level, parentTypes, childType, out, NULL, NULL, flags & ~REQSF_ROOT);
  273. else
  274. {
  275. ForEach(*children)
  276. buildJsonMsg(level, parentTypes, childType, out, NULL, &children->query(), flags & ~REQSF_ROOT);
  277. }
  278. checkNewlineJsonMsg(out, level, flags, -2);
  279. out.append(']');
  280. }
  281. else
  282. {
  283. IPropertyTree *childtree = NULL;
  284. if (reqTree)
  285. childtree = reqTree->queryPropTree(childname);
  286. buildJsonMsg(level, parentTypes, childType, out, childname, childtree, flags & ~REQSF_ROOT);
  287. }
  288. }
  289. }
  290. if (typeName)
  291. parentTypes.pop();
  292. checkNewlineJsonMsg(out, level, flags, -2); //end of children
  293. out.append("}");
  294. }
  295. else if (type->isArray())
  296. {
  297. bool skipContent = (typeName && !parentTypes.appendUniq(typeName)); // recursive
  298. const char* itemName = type->queryFieldName(0);
  299. IXmlType* itemType = type->queryFieldType(0);
  300. if (!itemName || !itemType)
  301. throw MakeStringException(-1,"*** Invalid array definition: tag=%s, itemName=%s", tag, itemName?itemName:"NULL");
  302. int startlen = out.length();
  303. if (tag)
  304. out.appendf("\"%s\": ", tag);
  305. out.append('{');
  306. checkNewlineJsonMsg(out, level, flags, 2);
  307. out.appendf("\"%s\": [", itemName);
  308. checkNewlineJsonMsg(out, level, flags, 2);
  309. if (!skipContent)
  310. {
  311. if (reqTree)
  312. {
  313. Owned<IPropertyTreeIterator> items = reqTree->getElements(itemName);
  314. ForEach(*items)
  315. {
  316. checkDelimitJsonMsg(out, level, flags);
  317. buildJsonMsg(level, parentTypes, itemType, out, NULL, &items->query(), flags & ~REQSF_ROOT);
  318. }
  319. }
  320. else
  321. buildJsonMsg(level, parentTypes, itemType, out, NULL, NULL, flags & ~REQSF_ROOT);
  322. if (typeName)
  323. parentTypes.pop();
  324. }
  325. checkNewlineJsonMsg(out, level, flags, -2);
  326. out.append(']');
  327. checkNewlineJsonMsg(out, level, flags, -2);
  328. out.append("}");
  329. }
  330. else // simple type
  331. {
  332. const char *parmval = (reqTree) ? reqTree->queryProp(NULL) : NULL;
  333. buildJsonAppendValue(type, out, tag, parmval, flags);
  334. }
  335. if (flags & REQSF_ROOT)
  336. {
  337. checkNewlineJsonMsg(out, level, flags, -2);
  338. out.append('}');
  339. }
  340. }
  341. static void buildJsonMsg(StringArray& parentTypes, IXmlType* type, StringBuffer& out, const char* tag, IPropertyTree *reqTree, unsigned flags)
  342. {
  343. unsigned level = 0;
  344. buildJsonMsg(level, parentTypes, type, out, tag, reqTree, flags);
  345. }
  346. };
  347. #endif // _JSONHELPERS_HPP__