1

The question is about the best practices when working with the Wrapper class.

Assumptions

We have PersonWrapper.

public class PersonWrapper {
    private String id;
    private String firstName;
    private String lastName;
    private Integer age;
    private  Integer height; 
    //other fields
}

Questions

  1. Do we need private properties? It's just a wrapper class. Is there any reason to not expose wrapper properties?
  2. What is the best way to populate Wrapper properties/fields?

A. Constructor

public class PersonWrapper {
    private String id;
    private String firstName;
    private String lastName;
    private Integer age;
    private Integer height; 
    //other fields

    public PersonWrapper(
       String id,
       String firstName,
       String lastName,
       Integer age,
       Integer height
    ) {
       this.id = id;
       this.firstName = firstName;
       this.lastName = lastName;
       this.age = age;
       this.height = height;
    }
}

PersonWrapper person = new PersonWrapper('123', 'John', 'Wick', 35, 185);

B. Direct assignment

public class PersonWrapper {
    public String id;
    public String firstName;
    public String lastName;
    public Integer age;
    public Integer height; 
    //other fields
}

PersonWrapper person = new PersonWrapper();
person.id = '123';
person.firstName = 'John';
person.lastName = 'Wick';
person.age = 35;
person.height = 185;

C. Builder

public class PersonWrapper {
    public String id;
    public String firstName;
    public String lastName;
    public Integer age;
    public Integer height; 
    //other fields
}

public class PersonWrapperBuilder {
    private PersonWrapper person;

    public PersonWrapperBuilder() {
        this.person = new PersonWrapper();
    }

    public PersonWrapperBuilder setId(String id) {
        this.person.id = id;
        return this;
    }

    public PersonWrapperBuilder setFirstName(String firstName) {
        this.person.firstName = firstName;
        return this;
    }

    //etc

    public PersonWrapper build() {
        return this.person;
    }
}

PersonWrapper person = 
   new PersonWrapperBuilder()
      .setId('123')
      .setFirstName('John')
      .build();

D. Something else?

My thoughts

Use Map<String, Object>

  • Do not overcomplicate. Map<String, Object> can replace simple wrappers. Especially when the wrapper will be used in one place.
return new Map<String, Object>{
    'id' => '123',
    'firstName' => 'John',
    'lastName' => 'Wick',
    'age' => 35,
    'height' => 185
};

Use constructor

  • When all fields in the wrapper are "required", a wrapper without all fields populated has no sense.
  • When you need to hide class properties (private) and expose them only via getters.
  • When you work with SObject.
public class Person {
    private String id;
    private String firstName;
    private String lastName;
    private Integer age;
    private Integer height; 
    //other fields

    public Person(User user) {
        this.id = user.Id;
        this.firstName = user.FirstName;
        //etc
    }
}
  • You have just a few fields e.g
public class Point {
   private Integer x; 
   private Integer y;

   public Point(Integer x, Integer y) {
      this.x = x;
      this.y = y;
   }
}

Use direct assignment

  • Use it for most cases to not complicate the logic.

Builder

  • When need to create wrapper step by step.
  • When additional validation/transformation is needed before the field is populated.
public PersonWrapperBuilder setFirstName(String firstName) {
    this.person.firstName = firstName.toUpperCase();
    return this;
}

Do you agree, disagree? Have some other thoughts, rules?

2 Answers 2

2

There are two anti-patterns in what you mention that I would avoid. One is use of long parameter lists (which the builder pattern avoids). The other is loosely typing your data. Your controller layer will be much clearer when every data type is concretely defined. Then, you don't have to wonder whether a particular Map<String, Object> is a Person, Animal, Vegetable, Mineral, etc. Concrete typing makes your code more self documenting and easier to read.

One note, if your wrapper is representing an SObject, that should be your constructor parameter, and you should use it as an attribute on the wrapper. The only other attributes you should add beyond that are:

  • fields that require manipulation before rendering which you specifically want to unit test (simple transforms can go in your javascript)
  • indirectly related data that you cannot pull in through table joins

Basically, when working with LWC I try to keep the Apex as light as possible, and copying a bunch of fields onto attributes is just a lot of extra code with no real benefit. If you take this approach, you may find the builder pattern unnecessary, and maybe even the wrapper itself.

1

Do we need private properties? It's just a wrapper class. Is there any reason to not expose wrapper properties?

You might need to use @JsonAccess, @AuraEnabled, public, global, and/or { get; set; }, depending on context. You might use a wrapper in a Visualforce page, a web component, for JSON.serialize or JSON.deserialize, or both, and they all have different rules.

What is the best way to populate Wrapper properties/fields?

There is no best way; they all have pros and cons. Constructors are best for a few fields (less than 4), direct assignment is most optimal for performance, while builder is debatably easier to read and maintain. One other obvious method you didn't mention is JSON serialization/deserialization. Also, wrappers can be serialized and deserialized in various contexts, like AuraEnabled methods, webservice methods, HttpGet/HttpPost/etc methods, and more. Sometimes you don't need a way to populate the data yourself (outside of unit tests).

Use Map<String, Object>

The downside is that you don't get compiler safety. If you're going to do this, consider an enum:

enum Field { FirstName, LastName, Email }
Map<Field, Object> wrapper = new Map<Field, Object> {
  Field.FirstName => 'Brian',
  Field.LastName => 'Fear',
  Field.Email => '[email protected]'
};
System.debug(wrapper.get(Field.FirstName));

This serializes and deserializes in JSON just fine. The only time I'd recommend a map with a string key is when the key may have values that can't be represented as symbols (e.g. First Name as a key must be a string in Apex). And I would only recommend that if you have no control over the data structure. Compiler safety helps prevents many common logic bugs from typos and/or case sensitivity.

When all fields in the wrapper are "required", a wrapper without all fields populated has no sense.

Up to about 4 or so. There's a hard limit of 32 parameters to any method, but I find that many parameters makes it hard to make sure you don't have a transposed input, etc, and also kind of hard to read. Use wrappers, sObject objects, direct assignment, etc.

D. Something else?

Wrappers can be automatically serialized and deserialized in webservice, RestResource/Http* classes/methods, and AuraEnabled methods. You might not need to ever initialize some wrappers directly.

Builder

Calling functions is slow. Consider using non-default getters or setters instead:

public String title { get; set { title = value?.capitalize(); } }

I would prefer direct assignment over builder patterns for most use cases. Builder patterns are better suited for complicated logic, like a SOQL builder and other service-layer type classes.

0

You must log in to answer this question.

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