converter.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. var AWS = require('../core');
  2. var util = AWS.util;
  3. var typeOf = require('./types').typeOf;
  4. var DynamoDBSet = require('./set');
  5. var NumberValue = require('./numberValue');
  6. AWS.DynamoDB.Converter = {
  7. /**
  8. * Convert a JavaScript value to its equivalent DynamoDB AttributeValue type
  9. *
  10. * @param data [any] The data to convert to a DynamoDB AttributeValue
  11. * @param options [map]
  12. * @option options convertEmptyValues [Boolean] Whether to automatically
  13. * convert empty strings, blobs,
  14. * and sets to `null`
  15. * @option options wrapNumbers [Boolean] Whether to return numbers as a
  16. * NumberValue object instead of
  17. * converting them to native JavaScript
  18. * numbers. This allows for the safe
  19. * round-trip transport of numbers of
  20. * arbitrary size.
  21. * @return [map] An object in the Amazon DynamoDB AttributeValue format
  22. *
  23. * @see AWS.DynamoDB.Converter.marshall AWS.DynamoDB.Converter.marshall to
  24. * convert entire records (rather than individual attributes)
  25. */
  26. input: function convertInput(data, options) {
  27. options = options || {};
  28. var type = typeOf(data);
  29. if (type === 'Object') {
  30. return formatMap(data, options);
  31. } else if (type === 'Array') {
  32. return formatList(data, options);
  33. } else if (type === 'Set') {
  34. return formatSet(data, options);
  35. } else if (type === 'String') {
  36. if (data.length === 0 && options.convertEmptyValues) {
  37. return convertInput(null);
  38. }
  39. return { S: data };
  40. } else if (type === 'Number' || type === 'NumberValue') {
  41. return { N: data.toString() };
  42. } else if (type === 'Binary') {
  43. if (data.length === 0 && options.convertEmptyValues) {
  44. return convertInput(null);
  45. }
  46. return { B: data };
  47. } else if (type === 'Boolean') {
  48. return { BOOL: data };
  49. } else if (type === 'null') {
  50. return { NULL: true };
  51. } else if (type !== 'undefined' && type !== 'Function') {
  52. // this value has a custom constructor
  53. return formatMap(data, options);
  54. }
  55. },
  56. /**
  57. * Convert a JavaScript object into a DynamoDB record.
  58. *
  59. * @param data [any] The data to convert to a DynamoDB record
  60. * @param options [map]
  61. * @option options convertEmptyValues [Boolean] Whether to automatically
  62. * convert empty strings, blobs,
  63. * and sets to `null`
  64. * @option options wrapNumbers [Boolean] Whether to return numbers as a
  65. * NumberValue object instead of
  66. * converting them to native JavaScript
  67. * numbers. This allows for the safe
  68. * round-trip transport of numbers of
  69. * arbitrary size.
  70. *
  71. * @return [map] An object in the DynamoDB record format.
  72. *
  73. * @example Convert a JavaScript object into a DynamoDB record
  74. * var marshalled = AWS.DynamoDB.Converter.marshall({
  75. * string: 'foo',
  76. * list: ['fizz', 'buzz', 'pop'],
  77. * map: {
  78. * nestedMap: {
  79. * key: 'value',
  80. * }
  81. * },
  82. * number: 123,
  83. * nullValue: null,
  84. * boolValue: true,
  85. * stringSet: new DynamoDBSet(['foo', 'bar', 'baz'])
  86. * });
  87. */
  88. marshall: function marshallItem(data, options) {
  89. return AWS.DynamoDB.Converter.input(data, options).M;
  90. },
  91. /**
  92. * Convert a DynamoDB AttributeValue object to its equivalent JavaScript type.
  93. *
  94. * @param data [map] An object in the Amazon DynamoDB AttributeValue format
  95. * @param options [map]
  96. * @option options convertEmptyValues [Boolean] Whether to automatically
  97. * convert empty strings, blobs,
  98. * and sets to `null`
  99. * @option options wrapNumbers [Boolean] Whether to return numbers as a
  100. * NumberValue object instead of
  101. * converting them to native JavaScript
  102. * numbers. This allows for the safe
  103. * round-trip transport of numbers of
  104. * arbitrary size.
  105. *
  106. * @return [Object|Array|String|Number|Boolean|null]
  107. *
  108. * @see AWS.DynamoDB.Converter.unmarshall AWS.DynamoDB.Converter.unmarshall to
  109. * convert entire records (rather than individual attributes)
  110. */
  111. output: function convertOutput(data, options) {
  112. options = options || {};
  113. var list, map, i;
  114. for (var type in data) {
  115. var values = data[type];
  116. if (type === 'M') {
  117. map = {};
  118. for (var key in values) {
  119. map[key] = convertOutput(values[key], options);
  120. }
  121. return map;
  122. } else if (type === 'L') {
  123. list = [];
  124. for (i = 0; i < values.length; i++) {
  125. list.push(convertOutput(values[i], options));
  126. }
  127. return list;
  128. } else if (type === 'SS') {
  129. list = [];
  130. for (i = 0; i < values.length; i++) {
  131. list.push(values[i] + '');
  132. }
  133. return new DynamoDBSet(list);
  134. } else if (type === 'NS') {
  135. list = [];
  136. for (i = 0; i < values.length; i++) {
  137. list.push(convertNumber(values[i], options.wrapNumbers));
  138. }
  139. return new DynamoDBSet(list);
  140. } else if (type === 'BS') {
  141. list = [];
  142. for (i = 0; i < values.length; i++) {
  143. list.push(AWS.util.buffer.toBuffer(values[i]));
  144. }
  145. return new DynamoDBSet(list);
  146. } else if (type === 'S') {
  147. return values + '';
  148. } else if (type === 'N') {
  149. return convertNumber(values, options.wrapNumbers);
  150. } else if (type === 'B') {
  151. return util.buffer.toBuffer(values);
  152. } else if (type === 'BOOL') {
  153. return (values === 'true' || values === 'TRUE' || values === true);
  154. } else if (type === 'NULL') {
  155. return null;
  156. }
  157. }
  158. },
  159. /**
  160. * Convert a DynamoDB record into a JavaScript object.
  161. *
  162. * @param data [any] The DynamoDB record
  163. * @param options [map]
  164. * @option options convertEmptyValues [Boolean] Whether to automatically
  165. * convert empty strings, blobs,
  166. * and sets to `null`
  167. * @option options wrapNumbers [Boolean] Whether to return numbers as a
  168. * NumberValue object instead of
  169. * converting them to native JavaScript
  170. * numbers. This allows for the safe
  171. * round-trip transport of numbers of
  172. * arbitrary size.
  173. *
  174. * @return [map] An object whose properties have been converted from
  175. * DynamoDB's AttributeValue format into their corresponding native
  176. * JavaScript types.
  177. *
  178. * @example Convert a record received from a DynamoDB stream
  179. * var unmarshalled = AWS.DynamoDB.Converter.unmarshall({
  180. * string: {S: 'foo'},
  181. * list: {L: [{S: 'fizz'}, {S: 'buzz'}, {S: 'pop'}]},
  182. * map: {
  183. * M: {
  184. * nestedMap: {
  185. * M: {
  186. * key: {S: 'value'}
  187. * }
  188. * }
  189. * }
  190. * },
  191. * number: {N: '123'},
  192. * nullValue: {NULL: true},
  193. * boolValue: {BOOL: true}
  194. * });
  195. */
  196. unmarshall: function unmarshall(data, options) {
  197. return AWS.DynamoDB.Converter.output({M: data}, options);
  198. }
  199. };
  200. /**
  201. * @api private
  202. * @param data [Array]
  203. * @param options [map]
  204. */
  205. function formatList(data, options) {
  206. var list = {L: []};
  207. for (var i = 0; i < data.length; i++) {
  208. list['L'].push(AWS.DynamoDB.Converter.input(data[i], options));
  209. }
  210. return list;
  211. }
  212. /**
  213. * @api private
  214. * @param value [String]
  215. * @param wrapNumbers [Boolean]
  216. */
  217. function convertNumber(value, wrapNumbers) {
  218. return wrapNumbers ? new NumberValue(value) : Number(value);
  219. }
  220. /**
  221. * @api private
  222. * @param data [map]
  223. * @param options [map]
  224. */
  225. function formatMap(data, options) {
  226. var map = {M: {}};
  227. for (var key in data) {
  228. var formatted = AWS.DynamoDB.Converter.input(data[key], options);
  229. if (formatted !== void 0) {
  230. map['M'][key] = formatted;
  231. }
  232. }
  233. return map;
  234. }
  235. /**
  236. * @api private
  237. */
  238. function formatSet(data, options) {
  239. options = options || {};
  240. var values = data.values;
  241. if (options.convertEmptyValues) {
  242. values = filterEmptySetValues(data);
  243. if (values.length === 0) {
  244. return AWS.DynamoDB.Converter.input(null);
  245. }
  246. }
  247. var map = {};
  248. switch (data.type) {
  249. case 'String': map['SS'] = values; break;
  250. case 'Binary': map['BS'] = values; break;
  251. case 'Number': map['NS'] = values.map(function (value) {
  252. return value.toString();
  253. });
  254. }
  255. return map;
  256. }
  257. /**
  258. * @api private
  259. */
  260. function filterEmptySetValues(set) {
  261. var nonEmptyValues = [];
  262. var potentiallyEmptyTypes = {
  263. String: true,
  264. Binary: true,
  265. Number: false
  266. };
  267. if (potentiallyEmptyTypes[set.type]) {
  268. for (var i = 0; i < set.values.length; i++) {
  269. if (set.values[i].length === 0) {
  270. continue;
  271. }
  272. nonEmptyValues.push(set.values[i]);
  273. }
  274. return nonEmptyValues;
  275. }
  276. return set.values;
  277. }
  278. /**
  279. * @api private
  280. */
  281. module.exports = AWS.DynamoDB.Converter;