You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

JsonEncoder.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. * Copyright 2000-2022 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.communication;
  17. import java.util.Collection;
  18. import java.util.List;
  19. import java.util.Map;
  20. import java.util.Map.Entry;
  21. import java.util.Set;
  22. import com.vaadin.client.ApplicationConnection;
  23. import com.vaadin.client.JsArrayObject;
  24. import com.vaadin.client.metadata.NoDataException;
  25. import com.vaadin.client.metadata.Property;
  26. import com.vaadin.client.metadata.Type;
  27. import com.vaadin.shared.Connector;
  28. import com.vaadin.shared.JsonConstants;
  29. import com.vaadin.shared.communication.UidlValue;
  30. import elemental.json.Json;
  31. import elemental.json.JsonArray;
  32. import elemental.json.JsonObject;
  33. import elemental.json.JsonValue;
  34. /**
  35. * Encoder for converting RPC parameters and other values to JSON for transfer
  36. * between the client and the server.
  37. *
  38. * Currently, basic data types as well as Map, String[] and Object[] are
  39. * supported, where maps and Object[] can contain other supported data types.
  40. *
  41. * TODO extensible type support
  42. *
  43. * @since 7.0
  44. */
  45. public class JsonEncoder {
  46. private JsonEncoder() {
  47. // private constructor to prevent initialization
  48. }
  49. /**
  50. * Encode a value to a JSON representation for transport from the client to
  51. * the server.
  52. *
  53. * @param value
  54. * value to convert
  55. * @param type
  56. * type information, not needed for all encoding tasks, such as
  57. * encoding a String
  58. * @param connection
  59. * application connection providing the context, not needed for
  60. * all encoding tasks, such as encoding a String
  61. * @return JSON representation of the value
  62. */
  63. @SuppressWarnings("unchecked")
  64. public static JsonValue encode(Object value, Type type,
  65. ApplicationConnection connection) {
  66. if (null == value) {
  67. return Json.createNull();
  68. } else if (value instanceof JsonValue) {
  69. return (JsonValue) value;
  70. } else if (value instanceof String[]) {
  71. String[] array = (String[]) value;
  72. JsonArray jsonArray = Json.createArray();
  73. for (int i = 0; i < array.length; ++i) {
  74. jsonArray.set(i, array[i]);
  75. }
  76. return jsonArray;
  77. } else if (value instanceof String) {
  78. return Json.create((String) value);
  79. } else if (value instanceof Boolean) {
  80. return Json.create((Boolean) value);
  81. } else if (value instanceof Number) {
  82. return Json.create(((Number) value).doubleValue());
  83. } else if (value instanceof Character) {
  84. return Json.create(String.valueOf(value));
  85. } else if (value instanceof Object[] && type == null) {
  86. // Non-legacy arrays handled by generated serializer
  87. return encodeLegacyObjectArray((Object[]) value, connection);
  88. } else if (value instanceof Enum) {
  89. return encodeEnum((Enum<?>) value);
  90. } else if (value instanceof Map) {
  91. return encodeMap((Map<Object, Object>) value, type, connection);
  92. } else if (value instanceof Connector) {
  93. Connector connector = (Connector) value;
  94. return Json.create(connector.getConnectorId());
  95. } else if (value instanceof Collection) {
  96. return encodeCollection((Collection<?>) value, type, connection);
  97. } else if (value instanceof UidlValue) {
  98. return encodeVariableChange((UidlValue) value, connection);
  99. } else {
  100. // First see if there's a custom serializer
  101. JSONSerializer<Object> serializer = null;
  102. if (type != null) {
  103. serializer = (JSONSerializer<Object>) type.findSerializer();
  104. if (serializer != null) {
  105. return serializer.serialize(value, connection);
  106. }
  107. }
  108. String transportType = getTransportType(value);
  109. if (transportType != null) {
  110. // Send the string value for remaining legacy types
  111. return Json.create(String.valueOf(value));
  112. } else if (type != null) {
  113. // And finally try using bean serialization logic
  114. try {
  115. JsArrayObject<Property> properties = type
  116. .getPropertiesAsArray();
  117. JsonObject jsonObject = Json.createObject();
  118. int size = properties.size();
  119. for (int i = 0; i < size; i++) {
  120. Property property = properties.get(i);
  121. Object propertyValue = property.getValue(value);
  122. Type propertyType = property.getType();
  123. JsonValue encodedPropertyValue = encode(propertyValue,
  124. propertyType, connection);
  125. jsonObject.put(property.getName(),
  126. encodedPropertyValue);
  127. }
  128. return jsonObject;
  129. } catch (NoDataException e) {
  130. throw new RuntimeException(
  131. "Can not encode " + type.getSignature(), e);
  132. }
  133. } else {
  134. throw new RuntimeException("Can't encode " + value.getClass()
  135. + " without type information");
  136. }
  137. }
  138. }
  139. private static JsonValue encodeVariableChange(UidlValue uidlValue,
  140. ApplicationConnection connection) {
  141. Object value = uidlValue.getValue();
  142. JsonArray jsonArray = Json.createArray();
  143. String transportType = getTransportType(value);
  144. if (transportType == null) {
  145. /*
  146. * This should not happen unless you try to send an unsupported type
  147. * in a legacy variable change from the client to the server.
  148. */
  149. String valueType = null;
  150. if (value != null) {
  151. valueType = value.getClass().getName();
  152. }
  153. throw new IllegalArgumentException(
  154. "Cannot encode object of type " + valueType);
  155. }
  156. jsonArray.set(0, Json.create(transportType));
  157. jsonArray.set(1, encode(value, null, connection));
  158. return jsonArray;
  159. }
  160. private static JsonValue encodeMap(Map<Object, Object> map, Type type,
  161. ApplicationConnection connection) {
  162. /*
  163. * As we have no info about declared types, we instead select encoding
  164. * scheme based on actual type of first key. We can't do this if there's
  165. * no first key, so instead we send some special value that the
  166. * server-side decoding must check for. (see #8906)
  167. */
  168. if (map.isEmpty()) {
  169. return Json.createArray();
  170. }
  171. Object firstKey = map.keySet().iterator().next();
  172. if (firstKey instanceof String) {
  173. return encodeStringMap(map, type, connection);
  174. } else if (type == null) {
  175. throw new IllegalStateException(
  176. "Only string keys supported for legacy maps");
  177. } else if (firstKey instanceof Connector) {
  178. return encodeConnectorMap(map, type, connection);
  179. } else {
  180. return encodeObjectMap(map, type, connection);
  181. }
  182. }
  183. private static JsonValue encodeChildValue(Object value, Type collectionType,
  184. int typeIndex, ApplicationConnection connection) {
  185. if (collectionType == null) {
  186. return encode(new UidlValue(value), null, connection);
  187. } else {
  188. assert collectionType.getParameterTypes() != null
  189. && collectionType.getParameterTypes().length > typeIndex
  190. && collectionType
  191. .getParameterTypes()[typeIndex] != null : "Proper generics required for encoding child value, assertion failed for "
  192. + collectionType;
  193. Type childType = collectionType.getParameterTypes()[typeIndex];
  194. return encode(value, childType, connection);
  195. }
  196. }
  197. private static JsonArray encodeObjectMap(Map<Object, Object> map, Type type,
  198. ApplicationConnection connection) {
  199. JsonArray keys = Json.createArray();
  200. JsonArray values = Json.createArray();
  201. assert type != null : "Should only be used for non-legacy types";
  202. for (Entry<?, ?> entry : map.entrySet()) {
  203. keys.set(keys.length(),
  204. encodeChildValue(entry.getKey(), type, 0, connection));
  205. values.set(values.length(),
  206. encodeChildValue(entry.getValue(), type, 1, connection));
  207. }
  208. JsonArray keysAndValues = Json.createArray();
  209. keysAndValues.set(0, keys);
  210. keysAndValues.set(1, values);
  211. return keysAndValues;
  212. }
  213. private static JsonValue encodeConnectorMap(Map<Object, Object> map,
  214. Type type, ApplicationConnection connection) {
  215. JsonObject jsonMap = Json.createObject();
  216. for (Entry<?, ?> entry : map.entrySet()) {
  217. Connector connector = (Connector) entry.getKey();
  218. JsonValue encodedValue = encodeChildValue(entry.getValue(), type, 1,
  219. connection);
  220. jsonMap.put(connector.getConnectorId(), encodedValue);
  221. }
  222. return jsonMap;
  223. }
  224. private static JsonValue encodeStringMap(Map<Object, Object> map, Type type,
  225. ApplicationConnection connection) {
  226. JsonObject jsonMap = Json.createObject();
  227. for (Entry<?, ?> entry : map.entrySet()) {
  228. String key = (String) entry.getKey();
  229. Object value = entry.getValue();
  230. jsonMap.put(key, encodeChildValue(value, type, 1, connection));
  231. }
  232. return jsonMap;
  233. }
  234. private static JsonValue encodeEnum(Enum<?> e) {
  235. return Json.create(e.toString());
  236. }
  237. private static JsonValue encodeLegacyObjectArray(Object[] array,
  238. ApplicationConnection connection) {
  239. JsonArray jsonArray = Json.createArray();
  240. for (int i = 0; i < array.length; ++i) {
  241. // TODO handle object graph loops?
  242. Object value = array[i];
  243. jsonArray.set(i, encode(value, null, connection));
  244. }
  245. return jsonArray;
  246. }
  247. private static JsonArray encodeCollection(Collection<?> collection,
  248. Type type, ApplicationConnection connection) {
  249. JsonArray jsonArray = Json.createArray();
  250. int idx = 0;
  251. for (Object o : collection) {
  252. JsonValue encodedObject = encodeChildValue(o, type, 0, connection);
  253. jsonArray.set(idx++, encodedObject);
  254. }
  255. if (collection instanceof Set) {
  256. return jsonArray;
  257. } else if (collection instanceof List) {
  258. return jsonArray;
  259. } else {
  260. throw new RuntimeException("Unsupport collection type: "
  261. + collection.getClass().getName());
  262. }
  263. }
  264. /**
  265. * Returns the transport type for the given value. Only returns a transport
  266. * type for internally handled values.
  267. *
  268. * @param value
  269. * The value that should be transported
  270. * @return One of the JsonEncode.VTYPE_ constants or null if the value
  271. * cannot be transported using an internally handled type.
  272. */
  273. private static String getTransportType(Object value) {
  274. if (value == null) {
  275. return JsonConstants.VTYPE_NULL;
  276. } else if (value instanceof String) {
  277. return JsonConstants.VTYPE_STRING;
  278. } else if (value instanceof Connector) {
  279. return JsonConstants.VTYPE_CONNECTOR;
  280. } else if (value instanceof Boolean) {
  281. return JsonConstants.VTYPE_BOOLEAN;
  282. } else if (value instanceof Integer) {
  283. return JsonConstants.VTYPE_INTEGER;
  284. } else if (value instanceof Float) {
  285. return JsonConstants.VTYPE_FLOAT;
  286. } else if (value instanceof Double) {
  287. return JsonConstants.VTYPE_DOUBLE;
  288. } else if (value instanceof Long) {
  289. return JsonConstants.VTYPE_LONG;
  290. } else if (value instanceof List) {
  291. return JsonConstants.VTYPE_LIST;
  292. } else if (value instanceof Set) {
  293. return JsonConstants.VTYPE_SET;
  294. } else if (value instanceof String[]) {
  295. return JsonConstants.VTYPE_STRINGARRAY;
  296. } else if (value instanceof Object[]) {
  297. return JsonConstants.VTYPE_ARRAY;
  298. } else if (value instanceof Map) {
  299. return JsonConstants.VTYPE_MAP;
  300. } else if (value instanceof Enum<?>) {
  301. // Enum value is processed as a string
  302. return JsonConstants.VTYPE_STRING;
  303. }
  304. return null;
  305. }
  306. }