javajsonrestletrestlet-2.0

Restlet converts JSON to LinkedHashMap instead of List<MyObject>?


This is what my Rest endpoint looks like

  @Post("json")
  public List<LogProcessorExpression> addLogProcessorExpression(
      final List<LogProcessorExpression> expressions) throws LPRestletException {
    if (expressions == null || expressions.isEmpty()) {
      return Collections.emptyList();
    }

    final Integer currentTenantId = Utils.getCurrentTenantId(getRequest());
    return customAttributesManager.addLogProcessorExpression(currentTenantId, expressions);
  }

the method it calls looks like

List<LogProcessorExpression> addLogProcessorExpression(final Integer currentTenantId,
                                                       final List<LogProcessorExpression> expressions)
      throws LPRestletException {
    final Map<String, LogProcessorExpression> cache = getCacheByCustomAttributeName(expressions);
    try {
      final List<Customattributesmetadata> cams =
          getCustomAttributesMetaDataForTenant(currentTenantId);

      for (final Customattributesmetadata metadata : cams) {
        if (cache.containsKey(metadata.getAttributecolumnname())) {
          metadata.setLogprocessorexpression(
              cache.get(metadata.getAttributecolumnname()).toString());
        }
        metadata.save();
      }
    } catch (final TorqueException e) {
      final String error = "Failed to update LogExpression custom attributes";
      LOGGER.error(error, e);
      throw new LPRestletException(error, e);
    }

    return expressions;
  }

which calls to other methods in chain. what I realized when accessing this endpoint as

 curl -v -H "Authorization:Basic Y3VyYasqrwqrjQGdtYWlsLmNvbTp0YXAzYWg=" \
     -H "Content-Type:application/json" \
     -d '[{"source": "ad", "attributePrefix": "ad_", "attributeName": "department"}]' \
     http://172.11.041.240:8080/api/rest/msp/attributes

That it returns

{
   "code" : 500
   "message" : "The server encountered an unexpected condition which prevented it from fulfilling the request",
}

When I looked at the logs, I see lines as

        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1515)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.shn.api.dto.LogProcessorExpression
        at com.shn.api.restlet.logprocessor.CustomAttributesManager.getCacheByCustomAttributeName(CustomAttributesManager.java:55)
        at com.shn.api.restlet.logprocessor.CustomAttributesManager.addLogProcessorExpression(CustomAttributesManager.java:24)
        at com.shn.api.restlet.logprocessor.CustomAttributeMetadataRestlet.addLogProcessorExpression(CustomAttributeMetadataRestlet.java:44)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at org.restlet.resource.ServerResource.doHandle(ServerResource.java:521)
        ... 67 more

Question
- Why is it not casting them to List<LogProcessorExpression>?
- What do I need to do to fix it?

UPDATE

Line 24 looks like

final Map<String, LogProcessorExpression> cache = getCacheByCustomAttributeName(expressions);  

Line 44 looks like

return customAttributesManager.addLogProcessorExpression(currentTenantId, expressions);  

getCacheByCustomAttributeName(expressions) looks like

  Map<String, LogProcessorExpression> getCacheByCustomAttributeName(
      final List<LogProcessorExpression> expressions) {
    if (expressions == null || expressions.isEmpty()) {
      return Collections.emptyMap();
    }

    final Map<String, LogProcessorExpression> attributeByExpression = new HashMap<>();
    for (final LogProcessorExpression expression : expressions) {
      attributeByExpression.put(expression.getAttributeName(), expression);
    }
    return attributeByExpression;
  }

Solution

  • I think restlet might not be sophisticated enough to extract the parameterized type List<LogProcessorExpression> from your handler method's parameter list.

    @Post("json")
    public List<LogProcessorExpression> addLogProcessorExpression(
      final List<LogProcessorExpression> expressions)
    

    It just takes List and presumably uses that when deserializing (with Jackson). Jackson uses LinkedHashMap as a deserialization target type when one isn't provided.

    Short of restlet implementing this better (maybe in a newer version?), a potential solution is to define a custom type

    class LogProcessorExpressionList extends ArrayList<LogProcessorExpression> {}
    

    and using that type as the parameter type

    @Post("json")
    public List<LogProcessorExpression> addLogProcessorExpression(
      final LogProcessorExpressionList expressions)
    

    Jackson can then extract the parameterized super type of the type LogProcessorExpressionList, which is ArrayList<LogProcessorExpression>, from which it can extract LogProcessorExpression as the target element type.