com.github.wnameless.json.unflattener.JsonUnflattener.java Source code

Java tutorial

Introduction

Here is the source code for com.github.wnameless.json.unflattener.JsonUnflattener.java

Source

/*
 *
 * Copyright 2015 Wei-Ming Wu
 *
 * 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 com.github.wnameless.json.unflattener;

import static org.apache.commons.lang3.Validate.notNull;

import java.io.IOException;
import java.io.StringWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.eclipsesource.json.JsonValue;
import com.eclipsesource.json.PrettyPrint;
import com.eclipsesource.json.WriterConfig;
import com.github.wnameless.json.flattener.PrintMode;

/**
 * 
 * {@link JsonUnflattener} provides a static {@link #unflatten(String)} method
 * to unflatten any flattened JSON string back to nested one.
 *
 * @author Wei-Ming Wu
 * 
 */
public final class JsonUnflattener {

    private static final String arrayIndex = "\\[\\s*\\d+\\s*\\]";
    private static final String objectComplexKey = "\\[\\s*\".*\"\\s*\\]";

    private final JsonValue root;

    private Character separator = '.';
    private PrintMode printMode = PrintMode.MINIMAL;

    /**
     * Creates a JSON unflattener.
     * 
     * @param json
     *          the JSON string
     */
    public JsonUnflattener(String json) {
        root = Json.parse(json);
    }

    private String objectKey() {
        return "[^" + Pattern.quote(separator.toString()) + "\\[\\]]+";
    }

    private Pattern keyPartPattern() {
        return Pattern.compile(arrayIndex + "|" + objectComplexKey + "|" + objectKey());
    }

    /**
     * A fluent setter to setup the separator within a key in the flattened JSON.
     * The default separator is a dot(.).
     * 
     * @param separator
     *          any character
     * @return this {@link JsonUnflattener}
     */
    public JsonUnflattener withSeparator(char separator) {
        this.separator = separator;
        return this;
    }

    /**
     * A fluent setter to setup a print mode of the {@link JsonUnflattener}. The
     * default print mode is minimal.
     * 
     * @param printMode
     *          a {@link PrintMode}
     * @return this {@link JsonUnflattener}
     */
    public JsonUnflattener withPrintMode(PrintMode printMode) {
        this.printMode = notNull(printMode);
        return this;
    }

    private WriterConfig getWriterConfig() {
        switch (printMode) {
        case REGULAR:
            return PrettyPrint.singleLine();
        case PRETTY:
            return WriterConfig.PRETTY_PRINT;
        default:
            return WriterConfig.MINIMAL;
        }
    }

    /**
     * Returns a JSON string of nested objects by the given flattened JSON string.
     * 
     * @param json
     *          a flattened JSON string
     * @return a JSON string of nested objects
     */
    public static String unflatten(String json) {
        return new JsonUnflattener(json).unflatten();
    }

    /**
     * Returns a JSON string of nested objects by the given flattened JSON string.
     * 
     * @return a JSON string of nested objects
     */
    public String unflatten() {
        StringWriter sw = new StringWriter();
        if (root.isArray()) {
            try {
                unflattenArray(root.asArray()).writeTo(sw, getWriterConfig());
            } catch (IOException e) {
            }
            return sw.toString();
        }
        if (!root.isObject()) {
            return root.toString();
        }

        JsonObject flattened = root.asObject();
        JsonValue unflattened = flattened.names().isEmpty() ? Json.object() : null;

        for (String key : flattened.names()) {
            JsonValue currentVal = unflattened;
            String objKey = null;
            Integer aryIdx = null;

            Matcher matcher = keyPartPattern().matcher(key);
            while (matcher.find()) {
                String keyPart = matcher.group();

                if (objKey != null ^ aryIdx != null) {
                    if (isJsonArray(keyPart)) {
                        currentVal = findOrCreateJsonArray(currentVal, objKey, aryIdx);
                        objKey = null;
                        aryIdx = extractIndex(keyPart);
                    } else { // JSON object
                        if (flattened.get(key).isArray()) { // KEEP_ARRAYS mode
                            flattened.set(key, unflattenArray(flattened.get(key).asArray()));
                        }
                        currentVal = findOrCreateJsonObject(currentVal, objKey, aryIdx);
                        objKey = extractKey(keyPart);
                        aryIdx = null;
                    }
                }

                if (objKey == null && aryIdx == null) {
                    if (isJsonArray(keyPart)) {
                        aryIdx = extractIndex(keyPart);
                        if (currentVal == null)
                            currentVal = Json.array();
                    } else { // JSON object
                        objKey = extractKey(keyPart);
                        if (currentVal == null)
                            currentVal = Json.object();
                    }
                }

                if (unflattened == null)
                    unflattened = currentVal;
            }

            setUnflattenedValue(flattened, key, currentVal, objKey, aryIdx);
        }

        try {
            unflattened.writeTo(sw, getWriterConfig());
        } catch (IOException e) {
        }
        return sw.toString();
    }

    private JsonArray unflattenArray(JsonArray array) {
        JsonArray unflattenArray = Json.array().asArray();

        for (JsonValue value : array) {
            if (value.isArray()) {
                unflattenArray.add(unflattenArray(value.asArray()));
            } else if (value.isObject()) {
                unflattenArray.add(
                        Json.parse(new JsonUnflattener(value.toString()).withSeparator(separator).unflatten()));
            } else {
                unflattenArray.add(value);
            }
        }

        return unflattenArray;
    }

    private String extractKey(String keyPart) {
        if (keyPart.matches(objectComplexKey))
            return keyPart.replaceAll("^\\[\\s*\"", "").replaceAll("\"\\s*\\]$", "");
        else
            return keyPart;
    }

    private static Integer extractIndex(String keyPart) {
        return Integer.valueOf(keyPart.replaceAll("[\\[\\]\\s]", ""));
    }

    private static boolean isJsonArray(String keyPart) {
        return keyPart.matches(arrayIndex);
    }

    private JsonValue findOrCreateJsonArray(JsonValue currentVal, String objKey, Integer aryIdx) {
        if (objKey != null) {
            JsonObject jsonObj = currentVal.asObject();

            if (jsonObj.get(objKey) == null) {
                JsonValue ary = Json.array();
                jsonObj.add(objKey, ary);

                return ary;
            }

            return jsonObj.get(objKey);
        } else { // aryIdx != null
            JsonArray jsonAry = currentVal.asArray();

            if (jsonAry.size() <= aryIdx || jsonAry.get(aryIdx).equals(Json.NULL)) {
                JsonValue ary = Json.array();
                assureJsonArraySize(jsonAry, aryIdx);
                jsonAry.set(aryIdx, ary);

                return ary;
            }

            return jsonAry.get(aryIdx);
        }
    }

    private JsonValue findOrCreateJsonObject(JsonValue currentVal, String objKey, Integer aryIdx) {
        if (objKey != null) {
            JsonObject jsonObj = currentVal.asObject();

            if (jsonObj.get(objKey) == null) {
                JsonValue obj = Json.object();
                jsonObj.add(objKey, obj);

                return obj;
            }

            return jsonObj.get(objKey);
        } else { // aryIdx != null
            JsonArray jsonAry = currentVal.asArray();

            if (jsonAry.size() <= aryIdx || jsonAry.get(aryIdx).equals(Json.NULL)) {
                JsonValue obj = Json.object();
                assureJsonArraySize(jsonAry, aryIdx);
                jsonAry.set(aryIdx, obj);

                return obj;
            }

            return jsonAry.get(aryIdx);
        }
    }

    private void setUnflattenedValue(JsonObject flattened, String key, JsonValue currentVal, String objKey,
            Integer aryIdx) {
        if (objKey != null) {
            currentVal.asObject().add(objKey, flattened.get(key));
        } else { // aryIdx != null
            assureJsonArraySize(currentVal.asArray(), aryIdx);
            currentVal.asArray().set(aryIdx, flattened.get(key));
        }
    }

    private void assureJsonArraySize(JsonArray jsonArray, Integer index) {
        while (index >= jsonArray.size()) {
            jsonArray.add(Json.NULL);
        }
    }

    @Override
    public int hashCode() {
        int result = 27;
        result = 31 * result + root.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (!(o instanceof JsonUnflattener))
            return false;
        return root.equals(((JsonUnflattener) o).root);
    }

    @Override
    public String toString() {
        return "JsonUnflattener{root=" + root + "}";
    }

}