11package com .github .jasminb .jsonapi ;
22
33import com .fasterxml .jackson .annotation .JsonInclude ;
4+ import com .fasterxml .jackson .core .JsonParser ;
45import com .fasterxml .jackson .core .JsonProcessingException ;
6+ import com .fasterxml .jackson .core .type .TypeReference ;
7+ import com .fasterxml .jackson .databind .JavaType ;
58import com .fasterxml .jackson .databind .JsonNode ;
69import com .fasterxml .jackson .databind .ObjectMapper ;
710import com .fasterxml .jackson .databind .node .ArrayNode ;
811import com .fasterxml .jackson .databind .node .ObjectNode ;
12+ import com .fasterxml .jackson .databind .type .MapType ;
13+ import com .fasterxml .jackson .databind .type .TypeFactory ;
914import com .github .jasminb .jsonapi .annotations .Id ;
1015import com .github .jasminb .jsonapi .annotations .Links ;
1116import com .github .jasminb .jsonapi .annotations .Meta ;
@@ -228,7 +233,7 @@ private <T> T readObjectInternal(byte [] data, Class<T> clazz, ResolverState res
228233 * @return collection of converted elements
229234 * @throws RuntimeException in case conversion fails
230235 */
231- public <T > List <T > readObjectCollection (byte [] data , Class <T > clazz ) {
236+ public <T > ResourceList <T > readObjectCollection (byte [] data , Class <T > clazz ) {
232237 return readObjectCollectionInternal (data , clazz , null );
233238 }
234239
@@ -241,7 +246,7 @@ public <T> List<T> readObjectCollection(byte [] data, Class<T> clazz) {
241246 * @return collection of converted elements
242247 * @throws RuntimeException in case conversion fails
243248 */
244- private <T > List <T > readObjectCollectionInternal (byte [] data , Class <T > clazz , ResolverState resolverState ) {
249+ private <T > ResourceList <T > readObjectCollectionInternal (byte [] data , Class <T > clazz , ResolverState resolverState ) {
245250
246251 try {
247252 JsonNode rootNode = objectMapper .readTree (data );
@@ -259,14 +264,25 @@ private <T> List<T> readObjectCollectionInternal(byte [] data, Class<T> clazz, R
259264 result .add (pojo );
260265 }
261266
262- return result ;
267+ ResourceList <T > wrapper = new ResourceList <>(result );
268+
269+ if (rootNode .has (LINKS )) {
270+ Map <String , Link > links = mapLinks (rootNode .get (LINKS ));
271+ wrapper .setLinks (links );
272+ }
273+
274+ if (rootNode .has (META )) {
275+ Map <String , ?> meta = mapMeta (rootNode .get (META ));
276+ wrapper .setMeta (meta );
277+ }
278+
279+ return wrapper ;
263280 } catch (RuntimeException e ) {
264281 throw e ;
265282 } catch (Exception e ) {
266283 throw new RuntimeException (e );
267284 }
268285
269-
270286 }
271287
272288 /**
@@ -514,6 +530,62 @@ private void handleRelationships(JsonNode source, Object object, Map<String, Obj
514530 }
515531 }
516532
533+ /**
534+ * Deserializes a <a href="http://jsonapi.org/format/#document-links">JSON-API links object</a> to a {@code Map}
535+ * keyed by the link name.
536+ * <p>
537+ * The {@code linksObject} may represent links in string form or object form; both are supported by this method.
538+ * </p>
539+ * <p>
540+ * E.g.
541+ * <pre>
542+ * "links": {
543+ * "self": "http://example.com/posts"
544+ * }
545+ * </pre>
546+ * </p>
547+ * <p>
548+ * or
549+ * <pre>
550+ * "links": {
551+ * "related": {
552+ * "href": "http://example.com/articles/1/comments",
553+ * "meta": {
554+ * "count": 10
555+ * }
556+ * }
557+ * }
558+ * </pre>
559+ * </p>
560+ *
561+ * @param linksObject a {@code JsonNode} representing a links object
562+ * @return a {@code Map} keyed by link name
563+ */
564+ private Map <String , Link > mapLinks (JsonNode linksObject ) {
565+ Map <String , Link > result = new HashMap <>();
566+
567+ Iterator <Map .Entry <String , JsonNode >> linkItr = linksObject .fields ();
568+
569+ while (linkItr .hasNext ()) {
570+ Map .Entry <String , JsonNode > linkNode = linkItr .next ();
571+ Link linkObj = new Link ();
572+
573+ linkObj .setHref (
574+ getLink (
575+ linkNode .getValue ()));
576+
577+ if (linkNode .getValue ().has (META )) {
578+ linkObj .setMeta (
579+ mapMeta (
580+ linkNode .getValue ().get (META )));
581+ }
582+
583+ result .put (linkNode .getKey (), linkObj );
584+ }
585+
586+ return result ;
587+ }
588+
517589 /**
518590 * Accepts a JsonNode which encapsulates a link. The link may be represented as a simple string or as
519591 * <a href="http://jsonapi.org/format/#document-links">link</a> object. This method introspects on the
@@ -533,6 +605,27 @@ private String getLink(JsonNode linkNode) {
533605 return linkNode .asText ();
534606 }
535607
608+ /**
609+ * Deserializes a <a href="http://jsonapi.org/format/#document-meta">JSON-API meta object</a> to a {@code Map}
610+ * keyed by the member names. Because {@code meta} objects contain arbitrary information, the values in the
611+ * map are of unknown type.
612+ *
613+ * @param metaNode a JsonNode representing a meta object
614+ * @return a Map of the meta information, keyed by member name.
615+ */
616+ private Map <String , ?> mapMeta (JsonNode metaNode ) {
617+ JsonParser p = objectMapper .treeAsTokens (metaNode );
618+ MapType mapType = TypeFactory .defaultInstance ()
619+ .constructMapType (HashMap .class , String .class , Object .class );
620+ try {
621+ return objectMapper .readValue (p , mapType );
622+ } catch (IOException e ) {
623+ // TODO: log? No recovery.
624+ }
625+
626+ return null ;
627+ }
628+
536629 /**
537630 * Creates relationship object by consuming provided 'data' node.
538631 * @param relationshipDataNode relationship data node
0 commit comments