dart-archive/rpc

RPC Encoding map error.

Opened this issue · 3 comments

I posted it on StackOverflow.

Essentially, it comes down to the method signature's required return type being a: Map<String,Object> doesnt parse as it should and resolved it entirely by setting it to Map<String,String> despite the fact that it was actually (and now successfully) returning a jsonified Map object from the RPC server to the client.

http://stackoverflow.com/questions/36163029/my-rcp-client-is-not-returning-a-deep-copy-of-an-object/36181858#36181858

One year later, I'm having a similar problem. The difference is that, instead of returning the Map<String,Object> on the function, I'm returning an object that contains the problematic Map.

Example:

@ApiMethod ( method: 'GET', path: 'test' )
MyMessage getTest() {
  MyMessage msg = new MyMessage()
    ..header = new Map<String,int>()
    ..content = new Map<String,String>();

    // populates the message with some stuff like
  msg.header["status"] = 200;
  msg.content["1"] = "Tomato tomato potato potato";

  return msg;
}

class MyMessage {
  Map<String,Object> header;
  Map<String,Object> content;

  MyMessage();
}

And just like yours, the response is:

{
  "header": {
    "status": {}
  },
  "content": {
    "1": {}
  }
}

Is there a recommended way to send generic Objects via RPC, or should I declare every single possible answer to my MyMessage class? Something like:

class MyMessage {
  Map<String,String> mapOfStrings;
  Map<String,int> mapOfIntegers;
  Map<String,MyObject> mapOfMyObjects;
  Map<String,List<MyObject>> mapWithListsOfMyObjects;
  ...

  MyMessage();
}

It really depends about how you and your team which to process content from your servers. Ultimately, we ended up having our results have a repsonse that contained a map of information. Success, Message, and Data.. We would then use that as the envelope which encapsulates the data we are sending from the server to the client and processed accordingly. Our service files will process and handle MyMessage because it would be passed into constructors etc which would attempt to extract information from MyMessage::data. Since it was Json, we noticed that the content we created would need to be able to process that. To do so, we would just have toJson and fromJson methods in the consumed classes.

class MyMessage {
    bool success;
    String message;
    dynamic data;
    MyMessage();
}

and then we would have our services do something akin to:

class Service {
    var _service;
    Future<MyNewClass> getData() async {
        return new MyNewClass.fromJson(await _service.get("hodor"));
    }
}

class MyNewClass {
    String name;
    toJson(){}
    fromJson(MyMessage result) => new MyNewClass()..name = result.data["name"];
}

Sadly RPC does not allow dynamic data inside the response. Your example would throw an exception like

Unhandled exception:
  RPC: Failed to parse API.
    MyMessage: data: Properties cannot be of type: 'dynamic'.

But anyway, we just tried storing our data in a String variable (instead of dynamic), by JSON.encoding everything and creating .toJSON() methods like you just told us. It works, but we need to recursively keep JSON.decoding our structures, since decoding only the top-most data structure doesn't work pretty well (and we have Lists inside Maps inside other Lists et cetra).

We'd love if there was a more elegant alternative to these recursive JSON.decodes though.