/** * $Id: $ * $Date: $ * */ package org.xmlsh.json; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.xmlsh.core.InvalidArgumentException; import org.xmlsh.core.XValue; import org.xmlsh.sh.shell.SerializeOpts; import org.xmlsh.types.TypeFamily; import org.xmlsh.types.xtypes.XValueList; import org.xmlsh.util.Util; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser.Feature; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.BooleanNode; import com.fasterxml.jackson.databind.node.ContainerNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.MissingNode; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.NumericNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.POJONode; import com.fasterxml.jackson.databind.node.TextNode; import com.fasterxml.jackson.databind.node.ValueNode; import com.fasterxml.jackson.databind.type.SimpleType; import com.fasterxml.jackson.databind.util.TokenBuffer; import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule; import com.fasterxml.jackson.dataformat.xml.XmlFactory; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Option; import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider; import com.jayway.jsonpath.spi.json.JsonProvider; import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; import com.jayway.jsonpath.spi.mapper.MappingProvider; public class JSONUtils { /* * NOTE TO DAL: * DO NOT try to extend XmlFatory to do renaming - its too invasive and complicated, * all the right places to override are final methods and its very tricky * Instead use the XMLRewritingStreamXXX classes * Or if you must fork the xml dataformat project and change the source * */ private static volatile ObjectMapper _theObjectMapper = null; private static volatile XmlFactory _theXmlFactory = null; private static volatile JacksonXmlModule _theXmlModule = null; private static volatile XmlMapper _theXmlMapper = null; private static Logger mLogger = LogManager.getLogger(); private final static JavaType JSON_NODE_TYPE = SimpleType .constructUnsafe(JsonNode.class); static { com.jayway.jsonpath.Configuration.setDefaults(new com.jayway.jsonpath.Configuration.Defaults() { { } private final JsonProvider jsonProvider = new JacksonJsonNodeJsonProvider(); private final MappingProvider mappingProvider = new JacksonMappingProvider(); @Override public JsonProvider jsonProvider() { return jsonProvider; } @Override public MappingProvider mappingProvider() { return mappingProvider; } public Set<Option> options() { return EnumSet.noneOf(Option.class); } }); } /* * TEST CODE ... needs to go into AWS * abstract class IgnoreVolumeTypeEnum * { * * @JsonSetter public abstract void setVolumeType(String vt); * * @JsonGetter public abstract String getVolumeType(String vt); * * * * } */ // Get a copy of the object mapper for configuring public static ObjectMapper newJsonObjectMapper() { return getJsonObjectMapper().copy(); } public static ObjectMapper getJsonObjectMapper() { return getJsonObjectMapper(null); } public static ObjectMapper getJsonObjectMapper(SerializeOpts opts) { // lets play and avoid syncronization // on the off chance this is concurrent 2 mappers are created and one gets GC'd if (_theObjectMapper == null) { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JaxbAnnotationModule()); mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); // mapper.configure(DeserializationFeature. x , on ); mapper.configure(Feature.ALLOW_SINGLE_QUOTES, true); mapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); mapper.configure(Feature.ALLOW_COMMENTS, true); mapper.configure(Feature.ALLOW_NON_NUMERIC_NUMBERS, true); mapper.configure(Feature.ALLOW_NUMERIC_LEADING_ZEROS, true); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); /* * Test code needs to go on AWS * mapper.addMixInAnnotations(com.amazonaws.services.ec2.model.EbsBlockDevice.class, IgnoreVolumeTypeEnum.class); */ // other completely global configurations if (_theObjectMapper == null) _theObjectMapper = mapper; } if( opts != null && _theObjectMapper.getSerializationConfig().isEnabled(SerializationFeature.INDENT_OUTPUT) != opts.getIndentJson() ) return _theObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, opts.getIndentJson()); return _theObjectMapper; } public static JacksonXmlModule getXmlModule() { // lets play and avoid syncronization // on the off chance this is concurrent 2 mappers are created and one gets GC'd if (_theXmlModule == null) { JacksonXmlModule module = new RenamingXmlModule(); if (_theXmlModule == null) _theXmlModule = module; } return _theXmlModule; } public static XmlFactory getXmlFactory() { // lets play and avoid syncronization // on the off chance this is concurrent 2 mappers are created and one gets GC'd if (_theXmlFactory == null) { XmlFactory factory = new XmlFactory(); if (_theXmlFactory == null) _theXmlFactory = factory; } return _theXmlFactory; } public static XmlMapper getXmlMapper() { // lets play and avoid syncronization // on the off chance this is concurrent 2 mappers are created and one gets GC'd if (_theXmlMapper == null) { XmlMapper mapper = newXmlMapper(); if (_theXmlMapper == null) _theXmlMapper = mapper; } return _theXmlMapper; } public static XmlMapper newXmlMapper() { XmlMapper mapper = new XmlMapper(getXmlFactory(), getXmlModule()); mapper.registerModule(new JaxbAnnotationModule()); return mapper; } public static ObjectWriter getObjectWriter() { return getJsonObjectMapper().writer(); } public static ObjectReader getObjectReader() { return getJsonObjectMapper().reader(); } public static JsonFactory getJsonFactory() { // Need to use ObjectMapper factory to get type mapping return getJsonObjectMapper().getFactory(); } public static JsonNode toJsonNode(String json) throws InvalidArgumentException { try { ObjectMapper mapper = getJsonObjectMapper(); JsonNode actualObj = mapper.readTree(json); return actualObj; } catch (Exception e) { Util.wrapException("Exception converting json value", e, InvalidArgumentException.class); return null; // SNH } } public static JsonNode toJsonType(XValue value) throws InvalidArgumentException { if (value.isNull()) return null; try { if (value.isJson()) return value.asJson(); ObjectMapper mapper = getJsonObjectMapper(); Object obj = value.getJavaNative(); if (obj instanceof Map) return mapper.convertValue(obj, ObjectNode.class); if (obj instanceof List) return mapper.convertValue(obj, ArrayNode.class);; if (obj instanceof Array) return mapper.convertValue(obj, ArrayNode.class);; if (obj instanceof Integer) return JsonNodeFactory.instance.numberNode((Integer) obj); if (obj instanceof Long) return JsonNodeFactory.instance.numberNode((Long) obj); if (obj instanceof Double) return JsonNodeFactory.instance.numberNode((Double) obj); if (obj instanceof Boolean) return JsonNodeFactory.instance.booleanNode((Boolean) obj); return mapper.convertValue(obj, jsonNodeClass()); } catch (Exception e) { Util.wrapException("Exception converting JSON value", e, InvalidArgumentException.class); return null; // SNH } } public static Class<JsonNode> jsonNodeClass() { return JsonNode.class; } public static JavaType jsonNodeType() { return JSON_NODE_TYPE; } public static NumericNode toJsonNumber(XValue arg) throws InvalidArgumentException { String str = null; if (arg.isJson()) { JsonNode j = arg.asJson(); if (j.isNumber()) return (NumericNode) j; else str = j.asText(); } else str = arg.toString(); try { ObjectMapper mapper = getJsonObjectMapper(); return mapper.readValue(str, NumericNode.class); } catch (Exception e) { Util.wrapException("Exception converting JSON value", e, InvalidArgumentException.class); return null; // SNH } } public static BooleanNode toJsonBoolean(XValue arg) throws InvalidArgumentException { try { boolean b = false; if (arg != null && !arg.isNull()) { if (arg.isJson()) b = arg.asJson().asBoolean(); else if (arg.isString()) { String s = arg.toString(); b = (s.equalsIgnoreCase("true") || s.equals("1")); } else b = arg.toBoolean(); } return JsonNodeFactory.instance.booleanNode(b); } catch (Exception e) { Util.wrapException("Exception converting JSON value", e, InvalidArgumentException.class); return null; // SNH } } public static String jsonToString(JsonNode value, boolean serialized) throws JsonProcessingException { if( value == null ) return ""; if( serialized ){ ObjectMapper mapper = getJsonObjectMapper(); return mapper.writeValueAsString(value); } else if( value.isValueNode() ) return value.asText(""); else return value.toString(); } /* * Read a json node from an input stream */ public static JsonNode readJsonNode(InputStream is) throws JsonProcessingException, IOException { ObjectMapper mapper = getJsonObjectMapper(); return mapper.readTree(is); } /* * Read an object from Json */ public static <T> T readJsonValue(InputStream is, Class<T> cls) throws JsonProcessingException, IOException { ObjectMapper mapper = getJsonObjectMapper(); return mapper.readValue(is, cls); } public static void writeJsonNode(JsonNode result, PrintStream os) throws JsonGenerationException, JsonMappingException, IOException { ObjectMapper mapper = getJsonObjectMapper(); mapper.writeValue(os, result); } public static NullNode nullValue() { return JsonNodeFactory.instance.nullNode(); } public static TextNode toJsonString(String string) { return JsonNodeFactory.instance.textNode(string); } public static TextNode toJsonString(XValue xv) { if (xv == null || xv.isNull()) return toJsonString((String) null); if (xv.isString()) return toJsonString(xv.toString()); if (xv.isAtomic()) return toJsonString(xv.toString()); ObjectMapper mapper = getJsonObjectMapper(); return mapper.convertValue(xv.asObject(), TextNode.class); } public static Object asJavaNative(JsonNode node) { if (node.isValueNode()) { ValueNode value = (ValueNode) node; if (value.isNumber()) return ((NumericNode) value).numberValue(); if (value.isBoolean()) return value.asBoolean(); if (value.isTextual()) return value.asText().toString(); if (value.isNull()) return null; } if (node.isArray()) { ArrayNode a = (ArrayNode) node; ArrayList<Object> al = new ArrayList<Object>(a.size()); for (JsonNode an : a) { al.add(asJavaNative(an)); } return al; } ObjectMapper mapper = getJsonObjectMapper(); if (node.isObject()) { return mapper.convertValue(node, Map.class); } return node.toString(); // WTF } public static void writeJsonNode(JsonNode value, OutputStream os, SerializeOpts opt) throws JsonGenerationException, JsonMappingException, IOException { ObjectMapper mapper = getJsonObjectMapper(opt); mapper.writeValue(os, value); } public static InputStream asInputStream(JsonNode value, SerializeOpts opt) throws JsonGenerationException, JsonMappingException, IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); writeJsonNode(value, bos, opt); bos.flush(); bos.close(); return new ByteArrayInputStream(bos.toByteArray()); } public static class JsonNodeBuilder extends TokenBuffer { public JsonNodeBuilder() { super(getJsonObjectMapper() , false); } public JsonNode build() throws JsonProcessingException, IOException { try ( JsonParser jp = asParser() ) { return getJsonObjectMapper().readTree(jp); } } }; public static JsonNodeBuilder createJsonNodeBuilder( ){ return new JsonNodeBuilder(); }; public static JsonGenerator createGenerator(OutputStream os, SerializeOpts jopts) throws IOException { JsonGenerator gen = getJsonFactory().createGenerator(os); if (jopts.getIndentJson() ) return gen.useDefaultPrettyPrinter(); return gen; } public static void safeClose(JsonGenerator generator) { if (generator != null) { try { generator.close(); } catch (IOException e) { mLogger.info("Exception closing JsonGenerator", e); } } } public static void safeClose(JsonParser parser) { if (parser != null) { try { parser.close(); } catch (IOException e) { mLogger.info("Exception closing JsonParser", e); } } } public static byte[] toByteArray(JsonNode value, SerializeOpts opt) throws JsonGenerationException, JsonMappingException, IOException { return getJsonObjectMapper().writeValueAsBytes(value); } public static JavaType getJavaType(Object obj) { return getJsonObjectMapper().constructType(obj.getClass()); } public static boolean isNullClass(Class<?> cls) { return cls == null || NullNode.class.isAssignableFrom(cls); } public static boolean isContainerClass(Class<?> cls) { return ContainerNode.class.isAssignableFrom(cls); } public static boolean isObjectClass(Class<?> cls) { return ObjectNode.class.isAssignableFrom(cls); } public static boolean isArrayClass(Class<?> cls) { return ArrayNode.class.isAssignableFrom(cls); } public static boolean isAtomicClass(Class<?> cls) { return ValueNode.class.isAssignableFrom(cls) && !(MissingNode.class.isAssignableFrom(cls) || POJONode.class.isAssignableFrom(cls)); } public static boolean isClassClass(Class<?> cls) { return JsonNodeType.class.isAssignableFrom(cls); } public static boolean isEmpty(JsonNode value) { return value.size() == 0; } public static ObjectNode newJsonObject() { return getJsonObjectMapper().createObjectNode(); } public static ArrayNode newJsonArray() { return getJsonObjectMapper().createArrayNode(); } public static List<XValue> asXList(Iterator<JsonNode> nodes) throws InvalidArgumentException { XValueList list = new XValueList(); while (nodes.hasNext()) list.add(XValue.newXValue(TypeFamily.JSON, nodes.next())); return list; } public static boolean isAtomic(Object value) { return isAtomicClass(value.getClass()); } public static void writeJsonNode(JsonGenerator gen, JsonNode result) throws JsonGenerationException, JsonMappingException, IOException { getObjectWriter().writeValue(gen,result); } } /* * Copyright (C) 2008-2014 David A. Lee. * * The contents of this file are subject to the "Simplified BSD License" (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.opensource.org/licenses/bsd-license.php * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. * See the License for the specific language governing rights and limitations under the License. * * The Original Code is: all this file. * * The Initial Developer of the Original Code is David A. Lee * * Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved. * * Contributor(s): David A. Lee */