Add constructor based binding support

See gh-8762

Co-authored-by: Stephane Nicoll <snicoll@pivotal.io>
This commit is contained in:
Madhura Bhave
2019-03-06 15:24:34 +01:00
committed by Stephane Nicoll
parent 8948c1dac6
commit 7ca589d43c
6 changed files with 754 additions and 3 deletions

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that can be used to specify the default value when binding to an immutable
* property.
*
* @author Madhura Bhave
* @since 2.2.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER })
@Documented
public @interface ConfigurationPropertyDefaultValue {
/**
* The default value of the property. Can be an array of values for collection or
* array-based properties.
* @return the default value of the property.
*/
String[] value();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2018 the original author or authors.
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,6 +62,7 @@ public class Binder {
static {
List<BeanBinder> binders = new ArrayList<>();
binders.add(new ConstructorParametersBinder());
binders.add(new JavaBeanBinder());
BEAN_BINDERS = Collections.unmodifiableList(binders);
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.ConfigurationPropertyDefaultValue;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.KotlinDetector;
import org.springframework.core.ResolvableType;
/**
* {@link BeanBinder} for constructor based binding.
*
* @author Madhura Bhave
* @author Stephane Nicoll
*/
class ConstructorParametersBinder implements BeanBinder {
private static boolean KOTLIN_PRESENT = KotlinDetector.isKotlinPresent();
private static final Map<Class<?>, Object> DEFAULT_TYPE_VALUES;
static {
Map<Class<?>, Object> values = new HashMap<>();
values.put(boolean.class, false);
values.put(byte.class, (byte) 0);
values.put(short.class, (short) 0);
values.put(int.class, 0);
values.put(long.class, (long) 0);
DEFAULT_TYPE_VALUES = Collections.unmodifiableMap(values);
}
@Override
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
Binder.Context context, BeanPropertyBinder propertyBinder) {
Bean bean = Bean.get(target);
if (bean == null) {
return null;
}
List<Object> bound = bind(propertyBinder, bean, context.getConverter());
return (T) BeanUtils.instantiateClass(bean.getConstructor(), bound.toArray());
}
private List<Object> bind(BeanPropertyBinder propertyBinder, Bean bean,
BindConverter converter) {
List<Object> boundParams = new ArrayList<>();
for (ConstructorParameter parameter : bean.getParameters().values()) {
Object bound = bind(parameter, propertyBinder);
if (bound == null) {
bound = getDefaultValue(parameter, bean, converter);
}
boundParams.add(bound);
}
return boundParams;
}
private Object getDefaultValue(ConstructorParameter parameter, Bean bean,
BindConverter converter) {
if (parameter.getDefaultValue() != null) {
return converter.convert(parameter.getDefaultValue(), parameter.getType(),
parameter.getAnnotations());
}
else {
Class<?> resolve = parameter.getType().resolve();
if (resolve != null && resolve.isPrimitive()) {
return (bean.kotlinType) ? null : DEFAULT_TYPE_VALUES.get(resolve);
}
}
return null;
}
private Object bind(ConstructorParameter parameter,
BeanPropertyBinder propertyBinder) {
String propertyName = parameter.getName();
ResolvableType type = parameter.getType();
return propertyBinder.bindProperty(propertyName, Bindable.of(type));
}
private static final class Bean {
private final boolean kotlinType;
private final Constructor<?> constructor;
private final Map<String, ConstructorParameter> parameters;
private Bean(boolean kotlinType, Constructor<?> constructor,
Map<String, ConstructorParameter> parameters) {
this.kotlinType = kotlinType;
this.constructor = constructor;
this.parameters = parameters;
}
public static Bean get(Bindable<?> bindable) {
if (bindable.getValue() != null) {
return null;
}
Class<?> type = bindable.getType().resolve(Object.class);
if (type.isEnum() || Modifier.isAbstract(type.getModifiers())) {
return null;
}
if (KOTLIN_PRESENT && KotlinDetector.isKotlinType(type)) {
Constructor<?> primaryConstructor = BeanUtils
.findPrimaryConstructor(type);
if (primaryConstructor != null
&& primaryConstructor.getParameterCount() > 0) {
return new Bean(true, primaryConstructor,
parseParameters(primaryConstructor));
}
}
else {
Constructor<?>[] constructors = type.getDeclaredConstructors();
if (constructors.length == 1 && constructors[0].getParameterCount() > 0) {
Constructor<?> constructor = constructors[0];
return new Bean(false, constructor, parseParameters(constructor));
}
}
return null;
}
private static Map<String, ConstructorParameter> parseParameters(
Constructor<?> constructor) {
Map<String, ConstructorParameter> parameters = new LinkedHashMap<>();
for (Parameter parameter : constructor.getParameters()) {
String name = parameter.getName();
ConfigurationPropertyDefaultValue[] annotationsByType = parameter
.getAnnotationsByType(ConfigurationPropertyDefaultValue.class);
String[] defaultValue = (annotationsByType.length > 0)
? annotationsByType[0].value() : null;
parameters.computeIfAbsent(name,
(s) -> new ConstructorParameter(name,
ResolvableType.forClass(parameter.getType()),
parameter.getDeclaredAnnotations(), defaultValue));
}
return parameters;
}
public Map<String, ConstructorParameter> getParameters() {
return this.parameters;
}
public Constructor<?> getConstructor() {
return this.constructor;
}
}
/**
* A constructor parameter being bound.
*/
private static class ConstructorParameter {
private final String name;
private final ResolvableType type;
private final Annotation[] annotations;
private final String[] defaultValue;
ConstructorParameter(String name, ResolvableType type, Annotation[] annotations,
String[] defaultValue) {
this.name = BeanPropertyName.toDashedForm(name);
this.type = type;
this.annotations = annotations;
this.defaultValue = defaultValue;
}
public String getName() {
return this.name;
}
public ResolvableType getType() {
return this.type;
}
public Annotation[] getAnnotations() {
return this.annotations;
}
public String[] getDefaultValue() {
return this.defaultValue;
}
}
}

View File

@@ -0,0 +1,286 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.context.properties.ConfigurationPropertyDefaultValue;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConstructorParametersBinder}.
*
* @author Madhura Bhave
*/
public class ConstructorParametersBinderTests {
private List<ConfigurationPropertySource> sources = new ArrayList<>();
private Binder binder;
@Before
public void setup() {
this.binder = new Binder(this.sources);
}
@Test
public void bindToClassShouldCreateBoundBean() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.int-value", "12");
source.put("foo.long-value", "34");
source.put("foo.boolean-value", "true");
source.put("foo.string-value", "foo");
source.put("foo.enum-value", "foo-bar");
this.sources.add(source);
ExampleValueBean bean = this.binder
.bind("foo", Bindable.of(ExampleValueBean.class)).get();
assertThat(bean.getIntValue()).isEqualTo(12);
assertThat(bean.getLongValue()).isEqualTo(34);
assertThat(bean.isBooleanValue()).isTrue();
assertThat(bean.getStringValue()).isEqualTo("foo");
assertThat(bean.getEnumValue()).isEqualTo(ExampleEnum.FOO_BAR);
}
@Test
public void bindToClassWhenHasNoPrefixShouldCreateBoundBean() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("int-value", "12");
source.put("long-value", "34");
source.put("boolean-value", "true");
source.put("string-value", "foo");
source.put("enum-value", "foo-bar");
this.sources.add(source);
ExampleValueBean bean = this.binder.bind(ConfigurationPropertyName.of(""),
Bindable.of(ExampleValueBean.class)).get();
assertThat(bean.getIntValue()).isEqualTo(12);
assertThat(bean.getLongValue()).isEqualTo(34);
assertThat(bean.isBooleanValue()).isTrue();
assertThat(bean.getStringValue()).isEqualTo("foo");
assertThat(bean.getEnumValue()).isEqualTo(ExampleEnum.FOO_BAR);
}
@Test
public void bindToAbstractClassWithShouldNotBind() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.name", "test");
this.sources.add(source);
boolean bound = this.binder.bind("foo", Bindable.of(ExampleAbstractBean.class))
.isBound();
assertThat(bound).isFalse();
}
@Test
public void bindToClassWithMultipleConstructorsShouldNotBind() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.int-value", "12");
this.sources.add(source);
boolean bound = this.binder
.bind("foo", Bindable.of(MultipleConstructorsBean.class)).isBound();
assertThat(bound).isFalse();
}
@Test
public void bindToClassWithOnlyDefaultConstructorShouldNotBind() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.int-value", "12");
this.sources.add(source);
boolean bound = this.binder.bind("foo", Bindable.of(DefaultConstructorBean.class))
.isBound();
assertThat(bound).isFalse();
}
@Test
public void bindToClassShouldBindNested() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.value-bean.int-value", "123");
source.put("foo.value-bean.long-value", "34");
source.put("foo.value-bean.string-value", "foo");
this.sources.add(source);
ExampleNestedBean bean = this.binder
.bind("foo", Bindable.of(ExampleNestedBean.class)).get();
assertThat(bean.getValueBean().getIntValue()).isEqualTo(123);
assertThat(bean.getValueBean().getLongValue()).isEqualTo(34);
assertThat(bean.getValueBean().isBooleanValue()).isFalse();
assertThat(bean.getValueBean().getStringValue()).isEqualTo("foo");
assertThat(bean.getValueBean().getEnumValue()).isNull();
}
@Test
public void bindToClassWithNoValueForPrimitiveShouldUseDefault() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.string-value", "foo");
this.sources.add(source);
ExampleValueBean bean = this.binder
.bind("foo", Bindable.of(ExampleValueBean.class)).get();
assertThat(bean.getIntValue()).isEqualTo(0);
assertThat(bean.getLongValue()).isEqualTo(0);
assertThat(bean.isBooleanValue()).isEqualTo(false);
assertThat(bean.getStringValue()).isEqualTo("foo");
}
@Test
public void bindToClassWithNoValueAndDefaultValueShouldUseDefault() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.string-value", "foo");
this.sources.add(source);
ExampleDefaultValueBean bean = this.binder
.bind("foo", Bindable.of(ExampleDefaultValueBean.class)).get();
assertThat(bean.getIntValue()).isEqualTo(5);
assertThat(bean.getStringsList()).containsOnly("a", "b", "c");
assertThat(bean.getCustomList()).containsOnly("x,y,z");
}
public static class ExampleValueBean {
private final int intValue;
private final long longValue;
private final boolean booleanValue;
private final String stringValue;
private final ExampleEnum enumValue;
public ExampleValueBean(int intValue, long longValue, boolean booleanValue,
String stringValue, ExampleEnum enumValue) {
this.intValue = intValue;
this.longValue = longValue;
this.booleanValue = booleanValue;
this.stringValue = stringValue;
this.enumValue = enumValue;
}
public int getIntValue() {
return this.intValue;
}
public long getLongValue() {
return this.longValue;
}
public boolean isBooleanValue() {
return this.booleanValue;
}
public String getStringValue() {
return this.stringValue;
}
public ExampleEnum getEnumValue() {
return this.enumValue;
}
}
public enum ExampleEnum {
FOO_BAR,
BAR_BAZ
}
@SuppressWarnings("unused")
public static class MultipleConstructorsBean {
public MultipleConstructorsBean(int intValue) {
this(intValue, 23L, "hello");
}
public MultipleConstructorsBean(int intValue, long longValue,
String stringValue) {
}
}
public abstract static class ExampleAbstractBean {
private final String name;
public ExampleAbstractBean(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public static class DefaultConstructorBean {
public DefaultConstructorBean() {
}
}
public static class ExampleNestedBean {
private final ExampleValueBean valueBean;
public ExampleNestedBean(ExampleValueBean valueBean) {
this.valueBean = valueBean;
}
public ExampleValueBean getValueBean() {
return this.valueBean;
}
}
public static class ExampleDefaultValueBean {
private final int intValue;
private final List<String> stringsList;
private final List<String> customList;
public ExampleDefaultValueBean(
@ConfigurationPropertyDefaultValue("5") int intValue,
@ConfigurationPropertyDefaultValue({ "a", "b",
"c" }) List<String> stringsList,
@ConfigurationPropertyDefaultValue("x,y,z") List<String> customList) {
this.intValue = intValue;
this.stringsList = stringsList;
this.customList = customList;
}
public int getIntValue() {
return this.intValue;
}
public List<String> getStringsList() {
return this.stringsList;
}
public List<String> getCustomList() {
return this.customList;
}
}
}

View File

@@ -348,13 +348,15 @@ public class JavaBeanBinderTests {
}
@Test
public void bindToClassWhenNoDefaultConstructorShouldReturnUnbound() {
public void bindToClassWhenNoDefaultConstructorShouldBind() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.value", "bar");
this.sources.add(source);
BindResult<ExampleWithNonDefaultConstructor> bean = this.binder.bind("foo",
Bindable.of(ExampleWithNonDefaultConstructor.class));
assertThat(bean.isBound()).isFalse();
assertThat(bean.isBound()).isTrue();
ExampleWithNonDefaultConstructor boundBean = bean.get();
assertThat(boundBean.getValue()).isEqualTo("bar");
}
@Test

View File

@@ -0,0 +1,208 @@
package org.springframework.boot.context.properties.bind
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.springframework.boot.context.properties.source.ConfigurationPropertyName
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource
/**
* Tests for `ConstructorParametersBinder`.
*
* @author Stephane Nicoll
*/
class KotlinConstructorParametersBinderTests {
@Test
fun `Bind to class should create bound bean`() {
val source = MockConfigurationPropertySource()
source.put("foo.int-value", "12")
source.put("foo.long-value", "34")
source.put("foo.boolean-value", "true")
source.put("foo.string-value", "foo")
source.put("foo.enum-value", "foo-bar")
val binder = Binder(source)
val bean = binder.bind("foo", Bindable.of(ExampleValueBean::class.java)).get()
assertThat(bean.intValue).isEqualTo(12)
assertThat(bean.longValue).isEqualTo(34)
assertThat(bean.booleanValue).isTrue()
assertThat(bean.stringValue).isEqualTo("foo")
assertThat(bean.enumValue).isEqualTo(ExampleEnum.FOO_BAR)
}
@Test
fun `Bind to class when has no prefix should create bound bean`() {
val source = MockConfigurationPropertySource()
source.put("int-value", "12")
source.put("long-value", "34")
source.put("boolean-value", "true")
source.put("string-value", "foo")
source.put("enum-value", "foo-bar")
val binder = Binder(source)
val bean = binder.bind(ConfigurationPropertyName.of(""),
Bindable.of(ExampleValueBean::class.java)).get()
assertThat(bean.intValue).isEqualTo(12)
assertThat(bean.longValue).isEqualTo(34)
assertThat(bean.booleanValue).isTrue()
assertThat(bean.stringValue).isEqualTo("foo")
assertThat(bean.enumValue).isEqualTo(ExampleEnum.FOO_BAR)
}
@Test
fun `Bind to data class should create bound bean`() {
val source = MockConfigurationPropertySource()
source.put("foo.int-value", "12")
source.put("foo.long-value", "34")
source.put("foo.boolean-value", "true")
source.put("foo.string-value", "foo")
source.put("foo.enum-value", "foo-bar")
val binder = Binder(source)
val bean = binder.bind("foo", Bindable.of(ExampleDataClassBean::class.java)).get()
assertThat(bean.intValue).isEqualTo(12)
assertThat(bean.longValue).isEqualTo(34)
assertThat(bean.booleanValue).isTrue()
assertThat(bean.stringValue).isEqualTo("foo")
assertThat(bean.enumValue).isEqualTo(ExampleEnum.FOO_BAR)
}
@Test
fun `Bind to class with multiple constructors and primary constructor should bind`() {
val source = MockConfigurationPropertySource()
source.put("foo.int-value", "12")
val binder = Binder(source)
val bindable = binder.bind("foo", Bindable.of(
MultipleConstructorsWithPrimaryConstructorBean::class.java))
assertThat(bindable.isBound).isTrue()
assertThat(bindable.get().intValue).isEqualTo(12)
}
@Test
fun `Bind to class with multiple constructors should not bind`() {
val source = MockConfigurationPropertySource()
source.put("foo.int-value", "12")
val binder = Binder(source)
val bindable = binder.bind("foo", Bindable.of(
MultipleConstructorsBean::class.java))
assertThat(bindable.isBound).isFalse()
}
@Test
fun `Bind to class with only default constructor should not bind`() {
val source = MockConfigurationPropertySource()
source.put("foo.int-value", "12")
val binder = Binder(source)
val bindable = binder.bind("foo", Bindable.of(
DefaultConstructorBean::class.java))
assertThat(bindable.isBound).isFalse()
}
@Test
fun `Bind to class should bind nested`() {
val source = MockConfigurationPropertySource()
source.put("foo.value-bean.int-value", "123")
source.put("foo.value-bean.long-value", "34")
source.put("foo.value-bean.boolean-value", "true")
source.put("foo.value-bean.string-value", "foo")
val binder = Binder(source)
val bean = binder.bind("foo", Bindable.of(ExampleNestedBean::class.java)).get()
assertThat(bean.valueBean.intValue).isEqualTo(123)
assertThat(bean.valueBean.longValue).isEqualTo(34)
assertThat(bean.valueBean.booleanValue).isTrue()
assertThat(bean.valueBean.stringValue).isEqualTo("foo")
assertThat(bean.valueBean.enumValue).isNull()
}
@Test
fun `Bind to class with no value for optional should use null`() {
val source = MockConfigurationPropertySource()
source.put("foo.string-value", "foo")
val binder = Binder(source)
val bean = binder.bind("foo", Bindable.of(
ExampleValueBean::class.java)).get()
assertThat(bean.intValue).isNull()
assertThat(bean.longValue).isNull()
assertThat(bean.booleanValue).isNull()
assertThat(bean.stringValue).isEqualTo("foo")
assertThat(bean.enumValue).isNull()
}
@Test
fun `Bind to class with no value for primitive should use default value`() {
val source = MockConfigurationPropertySource()
source.put("foo.string-value", "foo")
val binder = Binder(source)
val bean = binder.bind("foo", Bindable.of(
ExamplePrimitiveDefaultBean::class.java)).get()
assertThat(bean.intValue).isEqualTo(0)
assertThat(bean.longValue).isEqualTo(0)
assertThat(bean.booleanValue).isFalse()
assertThat(bean.stringValue).isEqualTo("foo")
assertThat(bean.enumValue).isNull()
}
@Test
fun `Bind to class with no value and default value should use default value`() {
val source = MockConfigurationPropertySource()
source.put("foo.string-value", "foo")
val binder = Binder(source)
val bean = binder.bind("foo", Bindable.of(
ExampleDefaultValueBean::class.java)).get()
assertThat(bean.intValue).isEqualTo(5)
assertThat(bean.stringsList).containsOnly("a", "b", "c")
assertThat(bean.customList).containsOnly("x,y,z")
}
@Test
fun `Bind to data class with no value should use default value`() {
val source = MockConfigurationPropertySource()
source.put("foo.enum-value", "foo-bar")
val binder = Binder(source)
val bean = binder.bind("foo", Bindable.of(ExampleDataClassBean::class.java)).get()
assertThat(bean.intValue).isEqualTo(5)
assertThat(bean.longValue).isEqualTo(42)
assertThat(bean.booleanValue).isFalse()
assertThat(bean.stringValue).isEqualTo("my data")
assertThat(bean.enumValue).isEqualTo(ExampleEnum.FOO_BAR)
}
class ExampleValueBean(val intValue: Int?, val longValue: Long?,
val booleanValue: Boolean?, val stringValue: String?,
val enumValue: ExampleEnum?)
class ExamplePrimitiveDefaultBean(val intValue: Int = 0, val longValue: Long = 0,
val booleanValue: Boolean = false, val stringValue: String?,
val enumValue: ExampleEnum?)
enum class ExampleEnum {
FOO_BAR,
BAR_BAZ
}
@Suppress("unused", "UNUSED_PARAMETER")
class MultipleConstructorsWithPrimaryConstructorBean(val intValue: Int) {
constructor(intValue: Int, longValue: Long) : this(intValue)
}
@Suppress("unused", "UNUSED_PARAMETER")
class MultipleConstructorsBean {
constructor(intValue: Int)
constructor(intValue: Int, longValue: Long) : this(intValue)
}
class DefaultConstructorBean {}
class ExampleNestedBean(val valueBean: ExampleValueBean)
class ExampleDefaultValueBean(val intValue: Int = 5,
val stringsList: List<String> = listOf("a", "b", "c"),
val customList: List<String> = listOf("x,y,z"))
data class ExampleDataClassBean(val intValue: Int = 5, val longValue: Long = 42,
val booleanValue: Boolean = false,
val stringValue: String = "my data",
val enumValue: ExampleEnum = ExampleEnum.BAR_BAZ)
}