/*******************************************************************************
* Copyright 2014 Miami-Dade County
*
* 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.sharegov.cirm.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import mjson.Json;
/**
*
* This class is thread safe (non blocking).
*
* @author Thomas Hilpold
*
*/
public class JsonUtil
{
/**
* Converts any json object structure into a flat sorted String to String map.
* The keys of the map are either the property names, index numbers (array) or
* a dotted path of those. (e.g. a.b.2.r.5)
* The returned map is safe to manipulate.
*
* For primitives a singleton map ("value", o.toString) is returned.
*
* @param o a json.
* @return
*/
public static Map<String, String> flatmap(Json o)
{
Map<String,String> m;
List<String> path = new ArrayList<String>();
if (o.isPrimitive())
m = Collections.singletonMap("value", o.getValue().toString());
else
m = flattenImpl(o, path);
return m;
}
/**
* Reursively traverse non primitive values using a path structure.
*
* @param o
* @param path
* @return
*/
private static SortedMap<String, String> flattenImpl(Json o, List<String> path)
{
SortedMap<String, String> result = new TreeMap<String, String>();
String curPathPrefix = getPathPrefix(path);
if (o.isObject())
{
Map<String, Json> ojmap = o.asJsonMap();
for (Map.Entry<String, Json> e : ojmap.entrySet())
{
if (e.getValue().isPrimitive())
if (e.getValue().isBoolean())
result.put(curPathPrefix + e.getKey(), "" + e.getValue().asBoolean());
else
result.put(curPathPrefix + e.getKey(), e.getValue().asString());
else
{
path.add(e.getKey());
// Recurse
Map<String, String> valueMap = flattenImpl(e.getValue(), path);
path.remove(path.size() -1);
result.putAll(valueMap);
}
}
}
else if (o.isArray())
{
List<Json> l = o.asJsonList();
int i = 0;
for (Json arrValue : l)
{
if (arrValue.isPrimitive())
result.put(curPathPrefix + i, arrValue.asString());
else
{
path.add("" + i);
// Recurse
Map<String, String> valueMap = flattenImpl(arrValue, path);
path.remove(path.size() -1);
result.putAll(valueMap);
}
i++;
}
} else
throw new IllegalStateException("Json at path "
+ getPathPrefix(path)
+ " is neither array nor object" + o.toString());
return result;
}
/**
* Generates a dotted path with a trailing dot.
* e.g. "a.b.c.d"
* @param path
* @return
*/
private static String getPathPrefix(List<String> path)
{
String result = "";
for (String s : path)
{
result += (s + ".");
}
return result;
}
/**
* Formats a json into an indented multi line string.
* The Json must not contain string literals containing any of { '{', '}', ',', ':' };
* TODO Improve String literal handling ("..inliteral..").
* @param j
* @return
*/
public static String formatFlattened(Json j)
{
StringBuffer result = new StringBuffer(1000);
Map<String,String> flatMap = flatmap(j);
for (Entry<String, String> flatE : flatMap.entrySet())
result.append(flatE.getKey() + " : " + flatE.getValue() + "\r\n");
return result.toString();
}
/**
* Formats a json into an indented multi line string.
* The Json must not contain string literals containing the quote char '"';
* TODO Improve String literal handling ("..inliteral..").
* @param j
* @return
*/
public static String format(Json j)
{
char[] in = j.toString().toCharArray();
StringBuffer out = new StringBuffer(in.length * 2);
int d = 0;
boolean inliteral = false;
for (int i = 0; i < in.length; i++)
{
if (in[i] == '"' || inliteral)
{
out.append(in[i]);
if(in[i] == '"')
inliteral = !inliteral;
}
else if (in[i] == '{')
{
out.append(in[i]);
out.append("\r\n");
d ++;
for (int curD = d; curD > 0; curD--)
out.append(" ");
}
else if (in[i] == '}')
{
d --;
out.append("\r\n");
for (int curD = d; curD > 0; curD--)
out.append(" ");
out.append(in[i]);
}
else if (in[i] == ',')
{
out.append(in[i]);
out.append("\r\n");
for (int curD = d; curD > 0; curD--)
out.append(" ");
}
else if (in[i] == ':')
{
out.append(" ");
out.append(in[i]);
out.append(" ");
}
else
out.append(in[i]);
}
return out.toString();
}
/**
* Always returns a Json.ArrayJson instance
* @param j
* @return
*/
public static Json ensureArray(Json j)
{
if (j.isArray()) return j;
else
return Json.array(j);
}
/**
* <p>
* Presents a set of JSON objects as a CSV table. The objects must
* all have the same form. The properties to use as well as the
* order of appearance is specified in the columns parameter.
* </p>
* @param arrayOfObjects A Json array (possible empty) containing
* JSON objects.
* @param properties A comma separate list of properties that make up
* the columns in the order specified.
* @param headers If not null, the table will contain a header row
* with the titles specified as a comma separated list in this argument.
* @return The generated CSV.
*/
public static String csvTable(Json arrayOfObjects, String properties, String headers)
{
StringBuilder sb = new StringBuilder();
if (headers != null)
{
String [] titles = headers.split(",");
for (int i = 0; i < titles.length - 1; i++)
sb.append(titles[i] + ",");
sb.append(titles[titles.length-1] + "\n");
}
String [] at = properties.split(",");
for (Json row : arrayOfObjects.asJsonList())
{
for (int i = 0; i < at.length - 1; i++)
{
Json x = row.at(at[i]);
sb.append(x.isNull() ? "null" : x.getValue().toString() + ",");
}
sb.append(row.at(at[at.length-1]) + "\n");
}
return sb.toString();
}
public static Json setIfMissing(Json object, String name, Object value)
{
if (!object.has(name) && value != null)
object.set(name, value);
return object;
}
}