jaypal singh
jaypal singh

Reputation: 77115

Loading Java Builder Object from Yaml file

I have created a Bean Class using Builder Pattern and having issues creating an object from a yaml file.

Here is a sample class (Actual class is quite big, this is just an excerpt incase if you wanted to answer with an example):

public class ClientBuilder {

    private final String firstName;
    private final String lastName;
    private final String displayName;

    private ClientBuilder(Builder builder) { 
        firstName   = builder.firstName; 
        lastName    = builder.lastName; 
        displayName = builder.displayName;
    }

    public static class Builder {

        private final String displayName; // Mandatory Attribute

        public Builder( String displayName ) {
            this.displayName = displayName;
        }

        private String firstName;
        private String lastName;

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

        public Builder setLastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public ClientBuilder build() {
            return new ClientBuilder(this);
        }
    }

    @Override
    public String toString() {
        StringBuffer sbf = new StringBuffer();
        sbf.append("New Company Object: \n");
        sbf.append("firstName   : " +  this.firstName   + "\n");
        sbf.append("lastName    : " +  this.lastName    + "\n");
        sbf.append("displayName : " +  this.displayName + "\n");
        return sbf.toString();
    }
}

I am using snakeyaml to load the file but any yaml api would work. Since the displayName is a mandatory param, I want to pass that value while creating the instance. The other params can be passed while creating the object but I would like the option to load them through yaml file.

I am able to load the yaml file if I use java bean. Is there a way to instantiate builder objects?

I tried:

InputStream input = new FileInputStream(new File("src/main/resources/client.yaml"));
Yaml yaml = new Yaml();
Builder builder = new Builder("Display Name");
builder = (Builder) yaml.loadAs(input, ClientBuilder.Builder.class);
ClientBuilder client = builder.build();
System.out.println(client.toString());

but I get following error:

Exception in thread "main" Can't construct a java object for tag:yaml.org,2002:com.xxx.xxx.xxx.ClientBuilder$Builder; exception=java.lang.NoSuchMethodException: com.xxx.xxx.xxx.ClientBuilder$Builder.<init>()
 in 'reader', line 2, column 1:
    firstName: "Jaypal"

Upvotes: 4

Views: 2969

Answers (2)

Sairam Krish
Sairam Krish

Reputation: 11701

SnakeYaml is a very powerful library & it provides support for creating instance based on constructor injection.

/**
 * create JavaBean
 */
public void testGetBeanAssumeClass() {
    String data = "--- !!org.yaml.snakeyaml.constructor.Person\nfirstName: Andrey\nage: 99";
    Object obj = construct(data);
    assertNotNull(obj);
    assertTrue("Unexpected: " + obj.getClass().toString(), obj instanceof Person);
    Person person = (Person) obj;
    assertEquals("Andrey", person.getFirstName());
    assertNull(person.getLastName());
    assertEquals(99, person.getAge().intValue());
}

/**
 * create instance using constructor arguments
 */
public void testGetConstructorBean() {
    String data = "--- !!org.yaml.snakeyaml.constructor.Person [ Andrey, Somov, 99 ]";
    Object obj = construct(data);
    assertNotNull(obj);
    assertTrue(obj.getClass().toString(), obj instanceof Person);
    Person person = (Person) obj;
    assertEquals("Andrey", person.getFirstName());
    assertEquals("Somov", person.getLastName());
    assertEquals(99, person.getAge().intValue());
}

Junit code sample can be viewed here. So your code still holds good. You may need to change yaml content with proper format. Once done, you are all set.

Upvotes: 2

Miki
Miki

Reputation: 7188

The exception is about there not being a no-arg constructor in Builder, as you probably figured out.

What you could do is to allow no-arg constructor for Builder and add corresponding setter and getter for displayName to it. Then simply throw an exception in build() if displayName is not set (or provide a default value for it). The exception can be a runtime one, or you could make it clear and add an explicit throws.

While it is not the prettiest solution, it should work just fine. The fact that the Builder is created without a mandatory argument should not matter, as it is the ClientBuilder that needs to be constructed properly (as the factory/builder is used to ensure that each instance of whatever it is building is correct).

I have no way to access any yaml parsing tools for Java currently, but if there is any way I could improve my answer, let me know - I will be happy to do so.

Upvotes: 1

Related Questions