PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/connector/src/main/java/com/restfb/DefaultJsonMapper.java

https://github.com/chrbayer84/GoodData-CL
Java | 586 lines | 311 code | 80 blank | 195 comment | 85 complexity | 8f9f912079b215bba0168eefecbcdd6b MD5 | raw file
Possible License(s): BSD-3-Clause
  1. /*
  2. * Copyright (c) 2010-2011 Mark Allen.
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. * THE SOFTWARE.
  21. */
  22. package com.restfb;
  23. import static com.restfb.json.JsonObject.NULL;
  24. import static com.restfb.util.ReflectionUtils.findFieldsWithAnnotation;
  25. import static com.restfb.util.ReflectionUtils.getFirstParameterizedTypeArgument;
  26. import static com.restfb.util.ReflectionUtils.isPrimitive;
  27. import static com.restfb.util.StringUtils.isBlank;
  28. import static com.restfb.util.StringUtils.trimToEmpty;
  29. import static java.util.Collections.unmodifiableList;
  30. import static java.util.Collections.unmodifiableSet;
  31. import static java.util.logging.Level.FINE;
  32. import static java.util.logging.Level.FINER;
  33. import static java.util.logging.Level.FINEST;
  34. import java.lang.reflect.Field;
  35. import java.math.BigDecimal;
  36. import java.math.BigInteger;
  37. import java.util.ArrayList;
  38. import java.util.HashMap;
  39. import java.util.HashSet;
  40. import java.util.List;
  41. import java.util.Map;
  42. import java.util.Map.Entry;
  43. import java.util.Set;
  44. import java.util.logging.Logger;
  45. import com.restfb.exception.FacebookJsonMappingException;
  46. import com.restfb.json.JsonArray;
  47. import com.restfb.json.JsonException;
  48. import com.restfb.json.JsonObject;
  49. import com.restfb.types.Post.Comments;
  50. import com.restfb.util.ReflectionUtils.FieldWithAnnotation;
  51. /**
  52. * Default implementation of a JSON-to-Java mapper.
  53. *
  54. * @author <a href="http://restfb.com">Mark Allen</a>
  55. */
  56. public class DefaultJsonMapper implements JsonMapper {
  57. /**
  58. * Logger.
  59. */
  60. private static final Logger logger = Logger.getLogger(DefaultJsonMapper.class.getName());
  61. /**
  62. * @see com.restfb.JsonMapper#toJavaList(String, Class)
  63. */
  64. @Override
  65. public <T> List<T> toJavaList(String json, Class<T> type) {
  66. json = trimToEmpty(json);
  67. if (isBlank(json))
  68. throw new FacebookJsonMappingException("JSON is an empty string - can't map it.");
  69. if (type == null)
  70. throw new FacebookJsonMappingException("You must specify the Java type to map to.");
  71. if (json.startsWith("{")) {
  72. // Sometimes Facebook returns the empty object {} when it really should be
  73. // returning an empty list [] (example: do an FQL query for a user's
  74. // affiliations - it's a list except when there are none, then it turns
  75. // into an object). Check for that special case here.
  76. if (isEmptyObject(json)) {
  77. if (logger.isLoggable(FINER))
  78. logger.finer("Encountered {} when we should've seen []. "
  79. + "Mapping the {} as an empty list and moving on...");
  80. return new ArrayList<T>();
  81. }
  82. // Special case: if the only element of this object is an array called
  83. // "data", then treat it as a list. The Graph API uses this convention for
  84. // connections and in a few other places, e.g. comments on the Post
  85. // object.
  86. // Doing this simplifies mapping, so we don't have to worry about having a
  87. // little placeholder object that only has a "data" value.
  88. try {
  89. JsonObject jsonObject = new JsonObject(json);
  90. String[] fieldNames = JsonObject.getNames(jsonObject);
  91. if (fieldNames != null) {
  92. boolean hasSingleDataProperty = fieldNames.length == 1 && "data".equals(fieldNames[0]);
  93. Object jsonDataObject = jsonObject.get("data");
  94. if (!hasSingleDataProperty && !(jsonDataObject instanceof JsonArray))
  95. throw new FacebookJsonMappingException("JSON is an object but is being mapped as a list "
  96. + "instead. Offending JSON is '" + json + "'.");
  97. json = jsonDataObject.toString();
  98. }
  99. } catch (JsonException e) {
  100. // Should never get here, but just in case...
  101. throw new FacebookJsonMappingException("Unable to convert Facebook response " + "JSON to a list of "
  102. + type.getName() + " instances. Offending JSON is " + json, e);
  103. }
  104. }
  105. try {
  106. List<T> list = new ArrayList<T>();
  107. JsonArray jsonArray = new JsonArray(json);
  108. for (int i = 0; i < jsonArray.length(); i++)
  109. list.add(toJavaObject(jsonArray.get(i).toString(), type));
  110. return unmodifiableList(list);
  111. } catch (FacebookJsonMappingException e) {
  112. throw e;
  113. } catch (Exception e) {
  114. throw new FacebookJsonMappingException("Unable to convert Facebook response " + "JSON to a list of "
  115. + type.getName() + " instances", e);
  116. }
  117. }
  118. /**
  119. * @see com.restfb.JsonMapper#toJavaObject(String, Class)
  120. */
  121. @Override
  122. @SuppressWarnings("unchecked")
  123. public <T> T toJavaObject(String json, Class<T> type) {
  124. verifyThatJsonIsOfObjectType(json);
  125. try {
  126. // Are we asked to map to JsonObject? If so, short-circuit right away.
  127. if (type.equals(JsonObject.class))
  128. return (T) new JsonObject(json);
  129. List<FieldWithAnnotation<Facebook>> fieldsWithAnnotation = findFieldsWithAnnotation(type, Facebook.class);
  130. Set<String> facebookFieldNamesWithMultipleMappings = facebookFieldNamesWithMultipleMappings(fieldsWithAnnotation);
  131. // If there are no annotated fields, assume we're mapping to a built-in
  132. // type. If this is actually the empty object, just return a new instance
  133. // of the corresponding Java type.
  134. if (fieldsWithAnnotation.size() == 0)
  135. if (isEmptyObject(json))
  136. return createInstance(type);
  137. else
  138. return toPrimitiveJavaType(json, type);
  139. // Facebook will sometimes return the string "null".
  140. // Check for that and bail early if we find it.
  141. if ("null".equals(json))
  142. return null;
  143. // Facebook will sometimes return the string "false" to mean null.
  144. // Check for that and bail early if we find it.
  145. if ("false".equals(json)) {
  146. if (logger.isLoggable(FINE))
  147. logger.fine("Encountered 'false' from Facebook when trying to map to " + type.getSimpleName()
  148. + " - mapping null instead.");
  149. return null;
  150. }
  151. JsonObject jsonObject = new JsonObject(json);
  152. T instance = createInstance(type);
  153. if (instance instanceof JsonObject)
  154. return (T) jsonObject;
  155. // For each Facebook-annotated field on the current Java object, pull data
  156. // out of the JSON object and put it in the Java object
  157. for (FieldWithAnnotation<Facebook> fieldWithAnnotation : fieldsWithAnnotation) {
  158. String facebookFieldName = getFacebookFieldName(fieldWithAnnotation);
  159. if (!jsonObject.has(facebookFieldName)) {
  160. if (logger.isLoggable(FINER))
  161. logger.finer("No JSON value present for '" + facebookFieldName + "', skipping. JSON is '" + json + "'.");
  162. continue;
  163. }
  164. fieldWithAnnotation.getField().setAccessible(true);
  165. // Set the Java field's value.
  166. //
  167. // If we notice that this Facebook field name is mapped more than once,
  168. // go into a special mode where we swallow any exceptions that occur
  169. // when mapping to the Java field. This is because Facebook will
  170. // sometimes return data in different formats for the same field name.
  171. // See issues 56 and 90 for examples of this behavior and discussion.
  172. if (facebookFieldNamesWithMultipleMappings.contains(facebookFieldName)) {
  173. try {
  174. fieldWithAnnotation.getField()
  175. .set(instance, toJavaType(fieldWithAnnotation, jsonObject, facebookFieldName));
  176. } catch (FacebookJsonMappingException e) {
  177. logMultipleMappingFailedForField(facebookFieldName, fieldWithAnnotation, json);
  178. } catch (JsonException e) {
  179. logMultipleMappingFailedForField(facebookFieldName, fieldWithAnnotation, json);
  180. }
  181. } else {
  182. fieldWithAnnotation.getField().set(instance, toJavaType(fieldWithAnnotation, jsonObject, facebookFieldName));
  183. }
  184. }
  185. return instance;
  186. } catch (FacebookJsonMappingException e) {
  187. throw e;
  188. } catch (Exception e) {
  189. throw new FacebookJsonMappingException("Unable to map JSON to Java. Offending JSON is '" + json + "'.", e);
  190. }
  191. }
  192. /**
  193. * Dumps out a log message when one of a multiple-mapped Facebook field name
  194. * JSON-to-Java mapping operation fails.
  195. *
  196. * @param facebookFieldName
  197. * The Facebook field name.
  198. * @param fieldWithAnnotation
  199. * The Java field to map to and its annotation.
  200. * @param json
  201. * The JSON that failed to map to the Java field.
  202. */
  203. protected void logMultipleMappingFailedForField(String facebookFieldName,
  204. FieldWithAnnotation<Facebook> fieldWithAnnotation, String json) {
  205. if (!logger.isLoggable(FINER))
  206. return;
  207. Field field = fieldWithAnnotation.getField();
  208. if (logger.isLoggable(FINER))
  209. logger.finer("Could not map '" + facebookFieldName + "' to " + field.getDeclaringClass().getSimpleName() + "."
  210. + field.getName() + ", but continuing on because '" + facebookFieldName
  211. + "' is mapped to multiple fields in " + field.getDeclaringClass().getSimpleName() + ". JSON is " + json);
  212. }
  213. /**
  214. * For a Java field annotated with the {@code Facebook} annotation, figure out
  215. * what the corresponding Facebook JSON field name to map to it is.
  216. *
  217. * @param fieldWithAnnotation
  218. * A Java field annotated with the {@code Facebook} annotation.
  219. * @return The Facebook JSON field name that should be mapped to this Java
  220. * field.
  221. */
  222. protected String getFacebookFieldName(FieldWithAnnotation<Facebook> fieldWithAnnotation) {
  223. String facebookFieldName = fieldWithAnnotation.getAnnotation().value();
  224. Field field = fieldWithAnnotation.getField();
  225. // If no Facebook field name was specified in the annotation, assume
  226. // it's the same name as the Java field
  227. if (isBlank(facebookFieldName)) {
  228. if (logger.isLoggable(FINEST))
  229. logger.finest("No explicit Facebook field name found for " + field
  230. + ", so defaulting to the field name itself (" + field.getName() + ")");
  231. facebookFieldName = field.getName();
  232. }
  233. return facebookFieldName;
  234. }
  235. /**
  236. * Finds any Facebook JSON fields that are mapped to more than 1 Java field.
  237. *
  238. * @param fieldsWithAnnotation
  239. * Java fields annotated with the {@code Facebook} annotation.
  240. * @return Any Facebook JSON fields that are mapped to more than 1 Java field.
  241. */
  242. protected Set<String> facebookFieldNamesWithMultipleMappings(List<FieldWithAnnotation<Facebook>> fieldsWithAnnotation) {
  243. Map<String, Integer> facebookFieldsNamesWithOccurrenceCount = new HashMap<String, Integer>();
  244. Set<String> facebookFieldNamesWithMultipleMappings = new HashSet<String>();
  245. // Get a count of Facebook field name occurrences for each
  246. // @Facebook-annotated field
  247. for (FieldWithAnnotation<Facebook> fieldWithAnnotation : fieldsWithAnnotation) {
  248. String fieldName = getFacebookFieldName(fieldWithAnnotation);
  249. int occurrenceCount =
  250. facebookFieldsNamesWithOccurrenceCount.containsKey(fieldName) ? facebookFieldsNamesWithOccurrenceCount
  251. .get(fieldName) : 0;
  252. facebookFieldsNamesWithOccurrenceCount.put(fieldName, occurrenceCount + 1);
  253. }
  254. // Pull out only those field names with multiple mappings
  255. for (Entry<String, Integer> entry : facebookFieldsNamesWithOccurrenceCount.entrySet())
  256. if (entry.getValue() > 1)
  257. facebookFieldNamesWithMultipleMappings.add(entry.getKey());
  258. return unmodifiableSet(facebookFieldNamesWithMultipleMappings);
  259. }
  260. /**
  261. * @see com.restfb.JsonMapper#toJson(Object)
  262. */
  263. @Override
  264. public String toJson(Object object) {
  265. // Delegate to recursive method
  266. return toJsonInternal(object).toString();
  267. }
  268. /**
  269. * Is the given {@code json} a valid JSON object?
  270. *
  271. * @param json
  272. * The JSON to check.
  273. * @throws FacebookJsonMappingException
  274. * If {@code json} is not a valid JSON object.
  275. */
  276. protected void verifyThatJsonIsOfObjectType(String json) {
  277. if (isBlank(json))
  278. throw new FacebookJsonMappingException("JSON is an empty string - can't map it.");
  279. if (json.startsWith("["))
  280. throw new FacebookJsonMappingException("JSON is an array but is being mapped as an object "
  281. + "- you should map it as a List instead. Offending JSON is '" + json + "'.");
  282. }
  283. /**
  284. * Recursively marshal the given {@code object} to JSON.
  285. * <p>
  286. * Used by {@link #toJson(Object)}.
  287. *
  288. * @param object
  289. * The object to marshal.
  290. * @return JSON representation of the given {@code object}.
  291. * @throws FacebookJsonMappingException
  292. * If an error occurs while marshaling to JSON.
  293. */
  294. protected Object toJsonInternal(Object object) {
  295. if (object == null)
  296. return NULL;
  297. if (object instanceof List<?>) {
  298. JsonArray jsonArray = new JsonArray();
  299. for (Object o : (List<?>) object)
  300. jsonArray.put(toJsonInternal(o));
  301. return jsonArray;
  302. }
  303. if (object instanceof Map<?, ?>) {
  304. JsonObject jsonObject = new JsonObject();
  305. for (Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
  306. if (!(entry.getKey() instanceof String))
  307. throw new FacebookJsonMappingException("Your Map keys must be of type " + String.class
  308. + " in order to be converted to JSON. Offending map is " + object);
  309. try {
  310. jsonObject.put((String) entry.getKey(), toJsonInternal(entry.getValue()));
  311. } catch (JsonException e) {
  312. throw new FacebookJsonMappingException("Unable to process value '" + entry.getValue() + "' for key '"
  313. + entry.getKey() + "' in Map " + object, e);
  314. }
  315. }
  316. return jsonObject;
  317. }
  318. if (isPrimitive(object))
  319. return object;
  320. if (object instanceof BigInteger)
  321. return ((BigInteger) object).longValue();
  322. if (object instanceof BigDecimal)
  323. return ((BigDecimal) object).doubleValue();
  324. // We've passed the special-case bits, so let's try to marshal this as a
  325. // plain old Javabean...
  326. List<FieldWithAnnotation<Facebook>> fieldsWithAnnotation =
  327. findFieldsWithAnnotation(object.getClass(), Facebook.class);
  328. JsonObject jsonObject = new JsonObject();
  329. Set<String> facebookFieldNamesWithMultipleMappings = facebookFieldNamesWithMultipleMappings(fieldsWithAnnotation);
  330. if (facebookFieldNamesWithMultipleMappings.size() > 0)
  331. throw new FacebookJsonMappingException("Unable to convert to JSON because multiple @"
  332. + Facebook.class.getSimpleName() + " annotations for the same name are present: "
  333. + facebookFieldNamesWithMultipleMappings);
  334. for (FieldWithAnnotation<Facebook> fieldWithAnnotation : fieldsWithAnnotation) {
  335. String facebookFieldName = getFacebookFieldName(fieldWithAnnotation);
  336. fieldWithAnnotation.getField().setAccessible(true);
  337. try {
  338. jsonObject.put(facebookFieldName, toJsonInternal(fieldWithAnnotation.getField().get(object)));
  339. } catch (Exception e) {
  340. throw new FacebookJsonMappingException("Unable to process field '" + facebookFieldName + "' for "
  341. + object.getClass(), e);
  342. }
  343. }
  344. return jsonObject;
  345. }
  346. /**
  347. * Given a {@code json} value of something like {@code MyValue} or {@code 123}
  348. * , return a representation of that value of type {@code type}.
  349. * <p>
  350. * This is to support non-legal JSON served up by Facebook for API calls like
  351. * {@code Friends.get} (example result: {@code [222333,1240079]}).
  352. *
  353. * @param <T>
  354. * The Java type to map to.
  355. * @param json
  356. * The non-legal JSON to map to the Java type.
  357. * @param type
  358. * Type token.
  359. * @return Java representation of {@code json}.
  360. * @throws FacebookJsonMappingException
  361. * If an error occurs while mapping JSON to Java.
  362. */
  363. @SuppressWarnings("unchecked")
  364. protected <T> T toPrimitiveJavaType(String json, Class<T> type) {
  365. if (String.class.equals(type)) {
  366. // If the string starts and ends with quotes, remove them, since Facebook
  367. // can serve up strings surrounded by quotes.
  368. if (json.length() > 1 && json.startsWith("\"") && json.endsWith("\"")) {
  369. json = json.replaceFirst("\"", "");
  370. json = json.substring(0, json.length() - 1);
  371. }
  372. return (T) json;
  373. }
  374. if (Integer.class.equals(type) || Integer.TYPE.equals(type))
  375. return (T) new Integer(json);
  376. if (Boolean.class.equals(type) || Boolean.TYPE.equals(type))
  377. return (T) new Boolean(json);
  378. if (Long.class.equals(type) || Long.TYPE.equals(type))
  379. return (T) new Long(json);
  380. if (Double.class.equals(type) || Double.TYPE.equals(type))
  381. return (T) new Double(json);
  382. if (Float.class.equals(type) || Float.TYPE.equals(type))
  383. return (T) new Float(json);
  384. if (BigInteger.class.equals(type))
  385. return (T) new BigInteger(json);
  386. if (BigDecimal.class.equals(type))
  387. return (T) new BigDecimal(json);
  388. throw new FacebookJsonMappingException("Don't know how to map JSON to " + type
  389. + ". Are you sure you're mapping to the right class? " + "Offending JSON is '" + json + "'.");
  390. }
  391. /**
  392. * Extracts JSON data for a field according to its {@code Facebook} annotation
  393. * and returns it converted to the proper Java type.
  394. *
  395. * @param fieldWithAnnotation
  396. * The field/annotation pair which specifies what Java type to
  397. * convert to.
  398. * @param jsonObject
  399. * "Raw" JSON object to pull data from.
  400. * @param facebookFieldName
  401. * Specifies what JSON field to pull "raw" data from.
  402. * @return A
  403. * @throws JsonException
  404. * If an error occurs while mapping JSON to Java.
  405. * @throws FacebookJsonMappingException
  406. * If an error occurs while mapping JSON to Java.
  407. */
  408. protected Object toJavaType(FieldWithAnnotation<Facebook> fieldWithAnnotation, JsonObject jsonObject,
  409. String facebookFieldName) throws JsonException, FacebookJsonMappingException {
  410. Class<?> type = fieldWithAnnotation.getField().getType();
  411. Object rawValue = jsonObject.get(facebookFieldName);
  412. // Short-circuit right off the bat if we've got a null value.
  413. if (NULL.equals(rawValue))
  414. return null;
  415. if (String.class.equals(type)) {
  416. // Special handling here for better error checking.
  417. // Since JsonObject.getString() will return literal JSON text even if it's
  418. // _not_ a JSON string, we check the marshaled type and bail if needed.
  419. // For example, calling JsonObject.getString("results") on the below
  420. // JSON...
  421. // {"results":[{"name":"Mark Allen"}]}
  422. // ... would return the string "[{"name":"Mark Allen"}]" instead of
  423. // throwing an error. So we throw the error ourselves.
  424. // Per Antonello Naccarato, sometimes FB will return an empty JSON array
  425. // instead of an empty string. Look for that here.
  426. if (rawValue instanceof JsonArray)
  427. if (((JsonArray) rawValue).length() == 0) {
  428. if (logger.isLoggable(FINER))
  429. logger.finer("Coercing an empty JSON array " + "to an empty string for " + fieldWithAnnotation);
  430. return "";
  431. }
  432. // If the user wants a string, _always_ give her a string.
  433. // This is useful if, for example, you've got a @Facebook-annotated string
  434. // field that you'd like to have a numeric type shoved into.
  435. // User beware: this will turn *anything* into a string, which might lead
  436. // to results you don't expect.
  437. return rawValue.toString();
  438. }
  439. if (Integer.class.equals(type) || Integer.TYPE.equals(type))
  440. return new Integer(jsonObject.getInt(facebookFieldName));
  441. if (Boolean.class.equals(type) || Boolean.TYPE.equals(type))
  442. return new Boolean(jsonObject.getBoolean(facebookFieldName));
  443. if (Long.class.equals(type) || Long.TYPE.equals(type))
  444. return new Long(jsonObject.getLong(facebookFieldName));
  445. if (Double.class.equals(type) || Double.TYPE.equals(type))
  446. return new Double(jsonObject.getDouble(facebookFieldName));
  447. if (Float.class.equals(type) || Float.TYPE.equals(type))
  448. return new BigDecimal(jsonObject.getString(facebookFieldName)).floatValue();
  449. if (BigInteger.class.equals(type))
  450. return new BigInteger(jsonObject.getString(facebookFieldName));
  451. if (BigDecimal.class.equals(type))
  452. return new BigDecimal(jsonObject.getString(facebookFieldName));
  453. if (List.class.equals(type))
  454. return toJavaList(rawValue.toString(), getFirstParameterizedTypeArgument(fieldWithAnnotation.getField()));
  455. String rawValueAsString = rawValue.toString();
  456. // Hack for issue 76 where FB will sometimes return a Post's Comments as
  457. // "[]" instead of an object type (wtf)
  458. if (Comments.class.isAssignableFrom(type) && rawValue instanceof JsonArray) {
  459. if (logger.isLoggable(FINE))
  460. logger.fine("Encountered comment array '" + rawValueAsString + "' but expected a "
  461. + Comments.class.getSimpleName() + " object instead. Working around that " + "by coercing into an empty "
  462. + Comments.class.getSimpleName() + " instance...");
  463. JsonObject workaroundJsonObject = new JsonObject();
  464. workaroundJsonObject.put("count", 0);
  465. workaroundJsonObject.put("data", new JsonArray());
  466. rawValueAsString = workaroundJsonObject.toString();
  467. }
  468. // Some other type - recurse into it
  469. return toJavaObject(rawValueAsString, type);
  470. }
  471. /**
  472. * Creates a new instance of the given {@code type}.
  473. *
  474. * @param <T>
  475. * Java type to map to.
  476. * @param type
  477. * Type token.
  478. * @return A new instance of {@code type}.
  479. * @throws FacebookJsonMappingException
  480. * If an error occurs when creating a new instance ({@code type} is
  481. * inaccessible, doesn't have a public no-arg constructor, etc.)
  482. */
  483. protected <T> T createInstance(Class<T> type) {
  484. String errorMessage =
  485. "Unable to create an instance of " + type + ". Please make sure that it's marked 'public' "
  486. + "and, if it's a nested class, is marked 'static'. " + "It should have a public, no-argument constructor.";
  487. try {
  488. return type.newInstance();
  489. } catch (IllegalAccessException e) {
  490. throw new FacebookJsonMappingException(errorMessage, e);
  491. } catch (InstantiationException e) {
  492. throw new FacebookJsonMappingException(errorMessage, e);
  493. }
  494. }
  495. /**
  496. * Is the given JSON equivalent to the empty object (<code>{}</code>)?
  497. *
  498. * @param json
  499. * The JSON to check.
  500. * @return {@code true} if the JSON is equivalent to the empty object,
  501. * {@code false} otherwise.
  502. */
  503. protected boolean isEmptyObject(String json) {
  504. return "{}".equals(json);
  505. }
  506. }