5

I am attempting to copy the properties from one Apex class instance to another (without hard coding the property names). See the below example:

public class Post{      

    public String name          { get; set; }
    public String topic         { get; set; }
    public String description   { get; set; }

    public Post(String jsonString){

        Post self = this;

        Post post = (Post)JSON.deserialize(jsonString, Post.class);

        self = post;

        //this = post; "Error: Expression cannot be assigned"

        System.debug(self); // {"name":"John Smith", "topic":"Test Topic", "description":"Test descriptions."}

        System.debug(this); // {"name":null, "topic":null, "description":null}

    }

}   

Post post = new Post('{"name":"John Smith", "topic":"Test Topic", "description":"Test descriptions."}');

//post is equal to {"name":null, "topic":null, "description":null}

My question is, how can I set the class properties en masse without explicitly referencing the class properties.

If that is not possible, is it possible to call a wrapper class' constructor during JSON deserialization?

For example:

public class Post{      

    public String apiKey { get; set; }

    public String name          { get; set; }
    public String topic         { get; set; }
    public String description   { get; set; }

    public Post(){
        this.apiKey = 'abcd1234';
    }

}   

Post post = (Post)JSON.deserialize('{"name":"John Smith", "topic":"Test Topic", "description":"Test descriptions."}', Post.class);

//post.apiKey == null;
3
  • 3
    Because of a basic lack of something like java.lang.Reflection, what you're trying to do is basically impossible.
    – sfdcfox
    Jan 3, 2018 at 17:24
  • You can use TestClass someClass = (TestClass)(Type.forName('TestClass').newInstance()); to create a new instance of a class with a no argument constructor, so for your second example, you could use this snippet to create a new instance (with some prefilled fields), then set the rest.. but I'm a little blank on how to do that without the using Schema... which is why this is a comment and not an answer.. Jan 3, 2018 at 17:55

2 Answers 2

4

No, you can't call the constructor after deserialization. You might want to look into the Factory Pattern. It would look something like:

public class Post
{
    public static Post factory(String payload)
    {
        Post instance = (Post)JSON.deserialize(payload, Post.class);
        instance.apiKey = 'someValue';
        return instance;
    }

    public String apiKey { get; private set; }
    public String someOtherProperty { get; private set; }
    public Post()
    {
        // do stuff
    }
}

You could even add an instance clone method if you want that looks something like:

public Post clone()
{
    return factory(JSON.serialize(this));
}
6
  • This won't work for specific cases, like a Visualforce controller/extension.
    – sfdcfox
    Jan 3, 2018 at 17:23
  • @sfdcfox Do tell? Why would that be a special case?
    – Adrian Larson
    Jan 3, 2018 at 17:23
  • Would it be possible to dynamically define the class property that is being set in the factory method? Something akin to instance['apiKey'] = 'someValue';
    – structure
    Jan 3, 2018 at 17:28
  • 1
    No. Not unless you convert your class definition to be more loosely typed using Map<String, String> or Map<String, Object> as a catchall property.
    – Adrian Larson
    Jan 3, 2018 at 17:29
  • In cases where you're forced to use a specific type of constructor, you're still forced to manually assign the attributes.
    – sfdcfox
    Jan 3, 2018 at 17:31
0

Based on your second example, I wrote up a snippet you could use to set some fields via a constructor, and the rest through a JSON String.

Using a DummyObject, with two fields included, one, set via a no argument constructor, and the other, set using the Copy method, which could take either a JSON string or a concrete object.

public class DummyObject {

    private String dummyField { get; set; }
    public String someField { get; set; }

    public DummyObject() {
        dummyField = 'Constructor Only'; 
    }

    public void Copy(DummyObject dummy) {
        this.someField = dummy.someField; 
    }
}

The snippet takes a JSON string with a field value for someField, then creates a Type based on the expected type (likely obtained from the JSON), before creating a new instance of DummyObject, setting half the fields. We then call Copy, using the JSON data provided, and deserializing it into a type.

String someJson = '{"someField":"Json Only"}';

Type typeOf = Type.forName('DummyObject');

DummyObject dummy = (DummyObject)typeOf.newInstance();

dummy.Copy(
    (DummyObject)JSON.deserialize(someJson, typeOf)
); 

System.debug(JSON.serializePretty(dummy)); 

I'm not sure how useful this is (a no argument constructor is kind of a pain, but you can have other constructors as well), and the real downside is that unless your properties are stored in a Map<String, Object>, you won't be able to get at them dynamically. However, by moving the Copy from the caller to the object itself, we can at least keep changes local to the file instead of requiring the parser to dig through the JSON and set field values,

You must log in to answer this question.

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