9

Let's say I have a virtual class and two classes that extend it and I am getting a JSON response that could be either one of those classes. Is there a way to deserialize the JSON into the appropriate class without examining it manually?

Here are my classes:

public with sharing virtual class BaseClass
{
public Integer id;
}

public with sharing class foo extends BaseClass
{
public String foo;
}

public with sharing class bar extends BaseClass
{
public Integer bar;
}

I want to be able to do something like

Json.deserialize(jsonstring,Class.BaseClass.Descendants)
7

If your JSON always has an id, but also has either a foo or a bar, I think the corresponding Apex would be

public class Thing {
    public Integer id;
    public String foo;
    public Integer bar;
}

You would deserialize into a Thing, then test whether foo or bar was null. There's no way to persuade the parser to pick the correct subclass.

| improve this answer | |
  • Well, my actual scenario is that my base class has a few fields and the classes extending it each have many fields. I am trying to integrate with an API that returns a whole bunch of JSON objects and I was hoping that I could do the JSON deserialization as part of my generic response handler (since all the error handling and such is the same no matter what object I'm getting) – Greg Grinberg Sep 12 '12 at 13:12
  • Is there anything in your data that tells you what kind of object you need to create, before you hit the object itself? – metadaddy Sep 12 '12 at 20:25
  • Yes, the endpoint for each object is different. I.E. /user, /account etc. I can definitely figure out which object to deserialize to. Was just hoping there was some generic way to do this. – Greg Grinberg Sep 12 '12 at 21:56
  • 1
    No way I know of to get the stock JSON parser to select the correct subclass :-( – metadaddy Feb 6 '14 at 19:04
  • 1
    More specifically, when you de-serialize into a virtual class, you're de-serializing into a virtual class. The de-serializer doesn't know how to determine which sub-class to use, and there's no known syntax for this. Perhaps for good reason: public virtual class A { integer x; } public class B extends A { } public class C extends A { }. All three classes have the same members, but possibly different functions. Which class should JSON de-serialize to? – sfdcfox Feb 7 '14 at 20:29
4

Based on the responses above, it's just not possible using the JSON.deserialize method. You have to develop your own parser. I too had the same requirement, and here's what I came up with. I'm sure it can be improved, since it's essentially parsing the JSON string 3 times.

In my case, I needed to take a parent QueryResult class, parse the JSON response into the class, but the class had a member variable called 'records'. This records member variable was a list of virtual classes, extended by 1 of 10 subclasses.

I parsed out the "type" attribute of the first record, which indicates what the subclass is, then used Type.forName() to deserialize the particular JSON token I needed into the correctly typed array.

private static QueryResult parseQueryResult(String jsonStr){
    QueryResult queryResult = (QueryResult)JSON.deserialize(jsonStr, ToolingAPI.QueryResult.class);
    queryResult.records = getQueryResultRecords(jsonStr);
    return queryResult;
}

/**
 * Helper method for parsing the QueryResult response and determining
 * which instance of QueryResultRecord to use
 */
private static List<QueryResultRecord> getQueryResultRecords(String jsonStr){

    String recordType = getRecordType(jsonStr);

    if(recordType != null){
        JSONParser parser = JSON.createParser(jsonStr);

        while (parser.nextToken() != null) {
            if ((parser.getText() == 'records')) {
                parser.nextToken();
                return (List<QueryResultRecord>)parser.readValueAs(Type.forName('List<ToolingAPI.'+recordType+'>'));
            }
        }
    }

    return null;
}

/**
 * Helper method for parsing type attribute from query result records in JSON response
 */
private static String getRecordType(String jsonStr){
    JSONParser parser = JSON.createParser(jsonStr);

    while (parser.nextToken() != null) {
        if ((parser.getText() == 'records')) {
            while(parser.nextToken() != null) {
                if(parser.getText() == 'attributes'){
                    while(parser.nextToken() != null){
                        if(parser.getText() == 'type'){
                            //Value of type attribute
                            parser.nextToken();
                            return parser.getText();
                        }
                    }
                }
            }
        }
    }
    return null;
}

Again, this can probably be improved on, but it gets the job done.

| improve this answer | |
2

I don't think it is possible.

JSON is a data only object, and has no knowledge of your classes.

You would need to embed the name of your class and check that when de-serializing.

| improve this answer | |
  • Is this possible in Apex? That is, is there an analogue to Java's instance.getClass().getName()? – Benj Sep 12 '12 at 14:20
  • 2
    Answering my own question! There is instance.class.getName() [salesforce.com/us/developer/docs/apexcode/Content/… ] – Benj Sep 12 '12 at 15:49
  • 2
    @Benj sadly .class.getName() doesn't work on the instance, it works on the class as if "class" were a static property. I'm not aware of any way to get a System.Type object from an actual instance of a class, only from the class itself. – ca_peterson Sep 13 '12 at 0:27
0

How about using Json.deserializeStrict to attempt each class? If your type lacks the necessary fields it will throw a System.JsonException which you can catch then attempt the next type?

eg:

List<Type> types = new List<Type>{
    BaseClass.class,
    Foo.class,
    Bar.class
};

BaseClass value;
for (Type t : types) {
    try {
        value = (BaseClass)Json.deserializeStrict(jsonstring, t);
    } catch (JsonException e) {
        continue;
    }
}

if (t instanceof Bar) ...
else if (t instanceof Foo) ...
else if (t instanceof BaseClass) ...
| improve this answer | |

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.