private void addConfigObjectMethods(final AbstractConfig config,
                                        final PropertyPath path,
                                        final Map<PropertyPath, Prop> propertyMap,
                                        final PropertyConsumer propConsumer,
                                        final boolean populateNullBranches) {
        LOGGER.trace("addConfigObjectMethods({}, {}, .....)", config, path);

        config.setBasePath(path);

        final Map<String, Prop> properties = PropertyUtil.getProperties(config);
        properties.forEach((k, prop) -> {
            LOGGER.trace("prop: {}", prop);

            // The prop may have a JsonPropery annotation that defines its name
            final String specifiedName = getNameFromAnnotation(prop);
            final String name = Strings.isNullOrEmpty(specifiedName)
                    ? prop.getName()
                    : specifiedName;

            final PropertyPath fullPath = path.merge(name);

            final Class<?> valueType = prop.getValueClass();

            final Object value = prop.getValueFromConfigObject();

            if (isSupportedPropertyType(valueType)) {

                // This is a leaf, i.e. a property so add it to our map
                propertyMap.put(fullPath, prop);

                if (propConsumer != null) {
                    // Now let the consumer do something to it
                    propConsumer.accept(fullPath, prop);
                }
            } else if (AbstractConfig.class.isAssignableFrom(valueType)) {
                // This must be a branch, i.e. config object so recurse into that
                if (value != null || populateNullBranches) {

                    final AbstractConfig childConfigObject;
                    if (value == null) {
                        try {
                            // if you have yaml like
                            //   pipeline:
                            //     referenceData:
                            // then referenceData will be null so we need to make one from the no args ctor
                            // to ensure we have a full tree
                            LOGGER.debug(() -> LogUtil.message("Constructing new default instance of {} on {}",
                                    valueType.getSimpleName(), config.getClass().getSimpleName()));
                            childConfigObject = (AbstractConfig) valueType.getConstructor().newInstance();
                            prop.setValueOnConfigObject(childConfigObject);
                        } catch (Exception e) {
                            throw new RuntimeException("Error constructing new instance of " + valueType, e);
                        }
                    } else {
                        childConfigObject = (AbstractConfig) value;
                    }

                    // Recurse into the child
                    addConfigObjectMethods(
                            childConfigObject,
                            fullPath,
                            propertyMap,
                            propConsumer,
                            populateNullBranches);
                }
            } else {
                // This is not expected
                throw new RuntimeException(LogUtil.message(
                        "Unexpected bean property of type [{}], expecting an instance of {}, or a supported type.",
                        valueType.getName(),
                        AbstractConfig.class.getSimpleName()));
            }
        });
    }
@param dbConfigProperty The config property object obtained from the database
@return The updated typed value from the object model
stroom-config/stroom-config-global-impl/src/main/java/stroom/config/global/impl/ConfigMapper.java