0

I'm attempting deserialize a list of a custom object in Apex, but got an error:

FATAL_ERROR System.JSONException: Apex Type unsupported in JSON: Object

This is because my ContactPostBack class contains the contact Map with <String, Object> which is not deserializable. Only the listed types here (bottom of page) can be deserialized:

https://developer.salesforce.com/docs/atlas.en-us.234.0.apexcode.meta/apexcode/langCon_apex_collections_maps.htm

private String url;
private Map<String, String> headers = new Map<String, String>();
private Map<String, Object> contact = new Map<String, Object>();

Changing the declaration of contact to Map<String, String> works, but Integers, Booleans, or other more complicated data structures will simply be converted to strings or throw errors.

I'd like the contact Map to contain multiple objects, like String, Integer, Boolean, or even other Maps or Lists. (I'm constructing a JSON object to post different endpoints).

Is it possible to create a "wrapper class" that contains the data types I can use?

Or is there a different implementation of JSON.deserialize that works for Object?

(Edit) - Included more code where the deserialization happens:

The ContactPostBack object:

public class ContactPostBack {

    private String url;
    private Map<String, String> headers = new Map<String, String>();

    // This only works when: Map<String, String> contacts ...
    // and the other references are changed to Map<String, String> type
    private Map<String, Object> contact = new Map<String, Object>();

    public ContactPostBack() {}

    public ContactPostBack(
        String url, Map<String, String> headers, Map<String, Object> contact
    ) {
        this.url = url;
        this.headers = headers;
        this.contact = contact;
    }

    public void setUrl(String url) {
        this.url = url;
    }
    public void addHeader(String key, String value) {
        this.headers.put(key, value);
    }
    public void addField(String key, Object value) {
        this.contact.put(key, value);
    }
    public Map<String, Object> getContact() {
        return this.contact;
    }
}

The actual ContactController2 class creates the List:

global class ContactController2 {

    private final Contact contact;
    private List<ContactPostBack> pbcList = new List<ContactPostBack>();
        
    public ContactController2(ApexPages.StandardController sc) {
        this.contact = (Contact)sc.getRecord();
        
        // Logic to initialize class...
        this.setData();
    }

    public String getContacts() {
        return JSON.serialize(pbcList);
    }

    public void setData() {
        // Business logic to add Contacts to pbcList
        ContactPostBack pbc = new ContactPostBack();
        // ...
        this.pbcList.add(pbc);
    }

}

Apex Trigger callout to POST to external API. This is where the error is thrown!!!

global class ContactPostBackController {

    @future (callout=true)
    public static void postContactChanges(String contacts) {
        // Receives the JSON serialized List<ContactPostBack> from the ContactController2
        postIt(contacts);
    }

    private static void postIt(String serializedContacts) {
        // Define request, response, and http objects
        HttpRequest req = new HttpRequest();
        HttpResponse res = new HttpResponse();
        Http http = new Http();
        
        // THIS IS WHERE THE ERROR IS THROWN
        List<ContactPostBack> contacts = (List<ContactPostBack>)JSON.deserialize(
            serializedContacts, List<ContactPostBack>.class
        );

        // Logic for sending contact to post back...
    }
}

Thanks!

4
  • Could you please edit your post to share the code you used to get the stated error? It is perfectly legitimate and in fact common to deserialize into Map<String, Object>.
    – Adrian Larson
    Commented Dec 8, 2021 at 16:29
  • 2
    Welcome to Salesforce Stack Exchange (SFSE)! I do not have much experience with serialization/deserialization, but the bottom of the page of the link you provided says that a Map is "serializable...only if it uses one of the following data types as a key" [emphasis added]. It says nothing of deserialization, and it says nothing of the values of the Map. Unless you have a different link that states differently, hopefully for you that might not be the issue.
    – Moonpie
    Commented Dec 8, 2021 at 16:35
  • @AdrianLarson Updated the post! Check it out.
    – Ian
    Commented Dec 8, 2021 at 23:07
  • @Moonpie I ended up realizing that wasn't what I was trying to do, oops! Thanks though.
    – Ian
    Commented Dec 8, 2021 at 23:08

1 Answer 1

2

For cases beyond what can be supported with JSON.deserialize, consider using JSON.deserializeUntyped. This mode is slightly more complicated, as it returns an Object that can be a List<Object> (if the JSON starts with [), or a Map<String, Object> (if the string starts with {). This can only return String, Boolean, Decimal, List<Object> and Map<String, Object> types, though (no automatic parsing of Dates, for example), but will always produce a valid return value assuming the JSON is itself valid.

As a hypothetical example, you can get the data you specify here from code like the following:

Map<String, Object> jsonResult = (Map<String, Object>)JSON.deserializeUntyped(jsonString);
String url = (String)jsonResult.get('url');
Map<String, Object> headers = (Map<String, Object>)jsonResult.get('headers');
Map<String, Object> contact = (Map<String, Object>)jsonResult.get('contact');

Note that due to a type-casting bug, you can't cast headers to a Map<String, String>, even though it should work logically, so you'll have a bit of extra code for casting values and so on, but this works reasonably well for most complicated JSON structures.

4
  • 1
    And List<Object>, ostensibly.
    – Adrian Larson
    Commented Dec 8, 2021 at 16:46
  • @AdrianLarson List<String> was a major typo. I did mean List<Object>, thanks for the catch.
    – sfdcfox
    Commented Dec 8, 2021 at 16:48
  • Wouldn't any List<Primitive> be supported in addition to List<Object>?
    – Adrian Larson
    Commented Dec 8, 2021 at 16:49
  • @AdrianLarson It doesn't do any type sniffing to see if it can return a more optimized list. It will always return exactly one of those types (and null, of course, which isn't really a type).
    – sfdcfox
    Commented Dec 8, 2021 at 16:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .