0

I have a JSON string of which I always know 2/3 of the structure will be the same, but the last third will be different based on the source. I want to try to build structured types for this in apex so I can easily and dynamically serialize each request down. I'm struggling with the best implementation though.

Essentially I will have an apex class for each "unique" third of the request, so I wanted to control and define the the unique there, but then I wanted to have the standard 2/3s stored in some either virtual or abstract class. Then each of the unique apex classes could say serialize the incoming json based on the standard / virtual / abstract two thirds and the unique 1/3 it knows about. Here is an example I have now, where this all lives in one unique apex class (I want to abstract out the first two classes, with the 'RequestDataDetails' class remaining in this apex class). Any thoughts are appreciated!

@TestVisible
private class Request {
    @TestVisible
    private List<RequestData> requestDataList {get;set;}
}
@TestVisible
private class RequestData {
    @TestVisible
    private RequestDataDetails details {get;set;}
}
@TestVisible
private class RequestDataDetails {
    @TestVisible
    private Contact contact {get;set;}
    @TestVisible
    private Custom_Object1__c custom {get;set;}
}
1
  • 2
    If you want part of your deserialized output to have dynamic structure, probably best to use a Map<String, Object> from that level on down.
    – Adrian Larson
    Commented Apr 30, 2019 at 19:03

1 Answer 1

0

I've had to do something along these lines for an on-going integration project (but with the added bonus that the "dynamic" part of the JSON I'm getting can re-use key names but use different types for the values, e.g. "price":"5.0" in one instance, and "price":5.0 in another)

I don't think I'd recommend the approach I had to settle on (regex to extract the "dynamic" bit of the payload with the "standardized" part of the payload containing enough information to tell me which class to use to deserialize the "dynamic" part).

General Approach

If I had to re-write the deserialization code for my integration project again, I'd probably tackle it like this:

  1. Deserialize the whole thing untyped to easily extract the "dynamic" bits
  2. Have a single class (inner class, or standalone, doesn't make too much of a difference) to do typed deserialization into for the "standardized" bits
  3. Do what you need to do to determine which class to "deserialize" the "dynamic" bits into
  4. Create a new instance of the specific class you determined you need to use for the "dynamic" bits, and pass the Map<String, Object> into the constructor

Taking a Map<String, Object> and building an object instance from it can be cumbersome. If the "dynamic" bits of your JSON contain relatively little data and aren't deeply nested, I'd probably look at just passing a Map<String, Object> into a constructor.

If your "dynamic" bits contain a lot of data and/or are deeply nested, then it might be worth it to take the penalty on CPU time to re-serialize the Map<String, Object> of your dynamic bits and then do a typed (not necessarily strict) deserialization of the result instead of writing a ton of constructors or doing a lot of explicit type casting.

Class Structure

Let's say that our example JSON is something like this

{
    "customerId":"12345",
    "submittedDate":"2019-04-30 14:40:00-0500",
    "orderType":"new",
    "dynamicBits":{
        "action":"subscribe",
        "price":5.00
    }
}

The "dynamicBits" don't really look all that dynamic, but let's pretend that we're using this payload structure for, say, initiating a support case as well.

"dynamicBits":"{
    "createdDate":"2019-04-30 14:40:00-0500",
    "supportSubject":"it isn't working :("
}

Still a rather contrived example...but it'll help illustrate things.

Each different structure of the dynamic bits could be their own class. To help things along later, you'll also need an abstract class that each of the "dynamic" subclasses extend.

public abstract class DynamicPayload{}

public class Subscription extends DynamicPayload{
    public String action;
    public Decimal price;
}

public class SupportTicket extends DynamicPayload{
    public String supportSubject;
    public DateTime createdDate;
}

The class to use for deserializing the "standardized" bits of the payload doesn't have much to talk about. It's basically a wrapper for your dynamic bits.

public class OverallPayload{
    public DateTime submittedDate;
    public String customerName;
    
    // This is the most interesting bit of this class
    // We don't want to have a "dynamicBits" variable name, because that would
    //   mean we would try to deserialize the dynamic bits (which would not end well)
    // Instead, declare a variable of the abstract class's type
    DynamicPayload innerPayload;
}

That abstract class we created earlier allows us to deserialize/initialize the dynamic bits and store the results in the overall payload without the need to do a bunch of type-casting wherever the deserialized data ends up being used. Some casting is still needed, but that's mostly limited to, say, Subscription explicitPayload = (Subscription)payload.innerPayload;

Applying the general approach outlined earlier

String jsonPayload = '{...}';

Map<String, Object> untyped = (Map<String, Object>)JSON.deserializeUntyped(jsonPayload);
String reSerializedDynamicBits = JSON.serialize(untyped.get('dynamicBits'));

OverallPayload deserialized = (OverallPayload)JSON.deserialize(jsonPayload, OverallPayload.class);

System.Type subclass;
// Some magic to determine which subclass to use

DynamicBits dynamicPayload = (DynamicBits)JSON.deserialize(reSerializedDynamicBits, subclass);

deserialized.innerPayload = dynamicPayload;

// and now you can send your OverallPayload instance to any method...

This approach can be helpful if you have a single REST endpoint in your org for whatever you're trying to do, and do centralized dispatch in the Apex class sitting at that endpoint.

Also helpful if you want to specify different requirements for a "valid" payload (each subclass of the abstract class can implement its own validation method)

You must log in to answer this question.

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