Source code

Java tutorial


Here is the source code for


 * Copyright 2015-2016 Patrick Jungermann
 * 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
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * See the License for the specific language governing permissions and
 * limitations under the License.
package com.github.pjungermann.config.types.json;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.pjungermann.config.Config;
import com.github.pjungermann.config.KeyBuilder;
import com.github.pjungermann.config.types.ConfigConversionException;
import com.github.pjungermann.config.types.ConfigConverter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;

import static java.util.Collections.synchronizedMap;

 * Converter from JSON to {@link Config} and vise versa.
 * @author Patrick Jungermann
public class JsonConverter implements ConfigConverter<ObjectNode> {

    private static final Logger LOGGER = LoggerFactory.getLogger(JsonConverter.class);

    static final ObjectMapper MAPPER;

    static {
        MAPPER = new ObjectMapper();
        MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

    private KeyBuilder keyBuilder;

    public void setKeyBuilder(@NotNull final KeyBuilder keyBuilder) {
        this.keyBuilder = keyBuilder;

    public Config from(@NotNull final ObjectNode convertible) {
        final Config config = new Config();
        putAll(synchronizedMap(config), convertible, "");

        return config;

    protected void putAll(@NotNull final Map<String, Object> config, @NotNull final ObjectNode convertible,
            @NotNull final String keyPrefix) {
        final Iterator<Map.Entry<String, JsonNode>> fields = convertible.fields();

        while (fields.hasNext()) {
            final Map.Entry<String, JsonNode> entry =;
            put(config, keyPrefix + entry.getKey(), entry.getValue());

    protected void put(@NotNull final Map<String, Object> config, @NotNull final String key,
            @NotNull JsonNode node) {
        if (node instanceof ObjectNode) {
            putAll(config, (ObjectNode) node, keyBuilder.toPrefix(key));

        config.put(key, extractValue(node));

    protected Object extractValue(@NotNull final JsonNode node) {
        if (node.isNull()) {
            return null;

        if (node.isBoolean()) {
            return node.booleanValue();

        if (node.isNumber()) {
            return node.numberValue();

        if (node.isTextual()) {
            return node.textValue();

        if (node.isBinary()) {
            try {
                return node.binaryValue();

            } catch (IOException e) {
                        String.format("failed to read binary value from node %s of type %s", node, node.getClass()),

        if (node instanceof ArrayNode) {
            return prepareEntries((ArrayNode) node);

        throw new UnsupportedOperationException(
                String.format("node %s of type %s is not supported", node, node.getClass()));

    protected ArrayList prepareEntries(@NotNull final ArrayNode valueList) {
        final ArrayList list = new ArrayList(valueList.size());

        for (JsonNode entry : valueList) {
            if (entry instanceof ObjectNode) {
                list.add(from((ObjectNode) entry));

            } else {

        return list;

    public ObjectNode to(@NotNull final Config config) throws ConfigConversionException {
        final HashMap<String, Object> map = new HashMap<>(config.size());

        new HashSet<>(config.keySet()).stream()
                .forEach(key -> addHierarchicalEntry(map, (String) key, config.get(key)));

        try {
            final String jsonString = MAPPER.writeValueAsString(map);
            return (ObjectNode) MAPPER.readTree(jsonString);

        } catch (JsonProcessingException e) {
            throw new ConfigConversionException(
                    "failed to serialize the config as json " + "and deserialize it into a JsonNode", e);

        } catch (IOException e) {
            // unlikely to happen, but part of the interface
            throw new ConfigConversionException("failed to read from the content", e);

    protected void addHierarchicalEntry(@NotNull final Map<String, Object> map, @NotNull final String key,
            final Object value) {
        final int index = key.indexOf(keyBuilder.getSeparator());
        if (index == -1) {
            map.put(key, value);

        final String rootKey = key.substring(0, index);
        if (!map.containsKey(rootKey)) {
            map.putIfAbsent(rootKey, new HashMap<>());

        addHierarchicalEntry((Map<String, Object>) map.get(rootKey), key.substring(index + 1), value);