4

I am trying to deserialize a list of jsons.

If I have only one json like so '{"apple":"green"}' I can parse it using JSON.deserializeUntyped:

Map<String, Object> input = (Map<String, Object>)JSON.deserializeUntyped('{"apple":"green"}');

However in my case I want to parse '[{"apple":"green"},{"banana":"yellow"}]'.

I tried using:

List<Map<String, Object>> input = (List<Map<String, Object>>)JSON.deserializeUntyped('[{"apple":"green"},{"banana":""}]');

However that throws a TypeException: Invalid conversion from runtime type List<ANY> to List<Map<String,ANY>>.

I can't make a class to parse this list of jsons as I don't know how long the list will be. I also don't know what the values will be.

What I do know is that it will be List<Map<String,String>>. But I can't get to parse it.

Any suggestions?

5

Apex's type system is weird. Perhaps someone wiser than me can explicate the underlying principle or limiting case here in terms of the language grammar in more detail, but I will only try to sketch it!

You can deserialize this JSON using JSON.deserializeUntyped(); you just have to deserialize it as an almost completely generic type: List<Object>. The JSON parser, when using deserializeUntyped(), doesn't have any information about the types of the elements of the outermost container - they could be Maps, Lists, or a heterogenous suite, and their own elements' types are also undefined.

Once the deserialization has been performed, you can cast the items themselves to their actual underlying types and take action on them:

List<Object> input = (List<Object>)JSON.deserializeUntyped('[{"apple":"green"},{"banana":""}]');

System.debug(input);

for (Object o : input) {
    System.debug((Map<String, Object>)o);
}

This gets you pretty much the log that you expect:

16:08:26:004 USER_DEBUG [5]|DEBUG|({apple=green}, {banana=})

16:08:26:004 USER_DEBUG [3]|DEBUG|{apple=green}

16:08:26:004 USER_DEBUG [3]|DEBUG|{banana=}

Naturally, you'll get a runtime exception if the actual type of one of the elements doesn't match your cast.

If you'd rather fully specify the type, you can do that too. This makes a sort of promise to the compiler about the types of the outermost collection's constituent members (a promise that the JSON must bear out!):

List<Map<String, String>> input = (List<Map<String, String>>)JSON.deserialize('[{"apple":"green"},{"banana":""}]', List<Map<String, String>>.class);
System.debug(input);
for (Object o : input) {
    System.debug((Map<String, Object>)o);
}

and you'll get the same debug output.

The .class construct is documented under System.Type. It gets you a reference to the Type object corresponding to that class.

| improve this answer | |
  • Thank you, this is awesome! I did try your second suggestion but did not put the .class at the end. What does that .class stand for? Where can I read about it? – Arthlete Jul 27 '18 at 20:13
  • 1
    .class is getting you a reference to the class corresponding to the type List<Map<String, String>>. There's a handful of places you use that in Apex, including Test.setMock(), but it's relatively uncommon because Apex doesn't have much runtime reflection support. I'm not sure where it's documented. I'll take a look. – David Reed Jul 27 '18 at 20:15
  • 1
    See my edit for a doc link. – David Reed Jul 27 '18 at 20:18
  • 1
    Super useful, thanks for sharing and thanks for responding! – Arthlete Jul 27 '18 at 20:19
  • 1
    @DavidReed Man, you are a pro. I am learning a lot from posts like this. Keep up the good work. – Satya S. Vema Jul 27 '18 at 20:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.