001package com.hfg.javascript; 002 003 004 005import java.io.File; 006import java.io.IOException; 007import java.io.InputStream; 008import java.io.Reader; 009 010import com.hfg.util.FileUtil; 011import com.hfg.util.StringUtil; 012import com.hfg.util.io.StreamUtil; 013 014//------------------------------------------------------------------------------ 015/** 016 JSON (serialized Javascript) utilities. 017 018 @author J. Alex Taylor, hairyfatguy.com 019 */ 020//------------------------------------------------------------------------------ 021// com.hfg Library 022// 023// This library is free software; you can redistribute it and/or 024// modify it under the terms of the GNU Lesser General Public 025// License as published by the Free Software Foundation; either 026// version 2.1 of the License, or (at your option) any later version. 027// 028// This library is distributed in the hope that it will be useful, 029// but WITHOUT ANY WARRANTY; without even the implied warranty of 030// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 031// Lesser General Public License for more details. 032// 033// You should have received a copy of the GNU Lesser General Public 034// License along with this library; if not, write to the Free Software 035// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 036// 037// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 038// jataylor@hairyfatguy.com 039//------------------------------------------------------------------------------ 040 041public class JSONUtil 042{ 043 //########################################################################### 044 // PUBLIC FUNCTIONS 045 //########################################################################### 046 047 //-------------------------------------------------------------------------- 048 /** 049 * Converts a JSON file back into objects. 050 */ 051 public static JsCollection toJsonObj(File inJsonFile) 052 throws IOException 053 { 054 if (! inJsonFile.exists()) 055 { 056 throw new IOException("The specified JSON file " + StringUtil.singleQuote(inJsonFile.getPath()) + " doesn't exist!"); 057 } 058 else if (! inJsonFile.canRead()) 059 { 060 throw new IOException("No read permissions for the specified JSON file " + StringUtil.singleQuote(inJsonFile.getPath()) + "!"); 061 } 062 063 return toJsonObj(FileUtil.read(inJsonFile)); 064 } 065 066 //-------------------------------------------------------------------------- 067 /** 068 * Converts a JSON Reader into objects. 069 */ 070 public static JsCollection toJsonObj(Reader inJson) 071 throws IOException 072 { 073 return toJsonObj(StreamUtil.readerToString(inJson)); 074 } 075 076 //-------------------------------------------------------------------------- 077 /** 078 * Converts a JSON stream back into objects. 079 */ 080 public static JsCollection toJsonObj(InputStream inJson) 081 throws IOException 082 { 083 return toJsonObj(StreamUtil.inputStreamToString(inJson)); 084 } 085 086 //-------------------------------------------------------------------------- 087 /** 088 * Converts a JSON string back into objects. 089 */ 090 public static JsCollection toJsonObj(CharSequence inJSON) 091 { 092 JsCollection obj = null; 093 094 if (inJSON != null) 095 { 096 CharSequence json = stripLineComments(inJSON); 097 098 if (json.toString().trim().startsWith("[")) 099 { 100 obj = new JsArray(json); 101 } 102 else if (json.toString().trim().startsWith("{")) 103 { 104 obj = new JsObjMap(json); 105 } 106 else 107 { 108 throw new RuntimeException("The JSON string must be enclosed in [] or {}."); 109 } 110 } 111 112 return obj; 113 } 114 115 //-------------------------------------------------------------------------- 116 /** 117 * Escapes JSON string values according to <a href='http://www.ietf.org/rfc/rfc4627.txt'>RFC 4627</a>. 118 * @param inValue the String to be escaped 119 * @return the escaped String value 120 */ 121 public static String escapeString(String inValue) 122 { 123 String escapedValue = null; 124 125 if (inValue != null) 126 { 127 StringBuilder buffer = new StringBuilder(); 128 129 for (int i = 0; i < inValue.length(); i++) 130 { 131 char theChar = inValue.charAt(i); 132 switch (theChar) 133 { 134 case '"': 135 // Prepend quotes with an escape (if one isn't already present) 136// buffer.append((0 == buffer.length() || buffer.charAt(buffer.length() - 1) != '\\' ? "\\" : "") + "\""); 137 buffer.append("\\\""); 138 break; 139 140 case '\\': 141/* if (i < inValue.length() - 1 142 && inValue.charAt(i + 1) == '\'') // No need to escape single quotes in a double quoted string 143 { 144 // Do nothing. 145 } 146 else 147*/ { 148 buffer.append("\\\\"); 149// buffer.append("\\"); 150 } 151 break; 152 153 case '\r': 154 buffer.append("\\r"); 155 break; 156 157 case '\n': 158 buffer.append("\\n"); 159 break; 160 161 case '\t': 162 buffer.append("\\t"); 163 break; 164 165 default: 166 // A less common control character? 167 if(theChar >='\u0000' 168 && theChar <='\u001F') 169 { 170 String hexString = Integer.toHexString(theChar); 171 buffer.append("\\u"); 172 // Prepend with the correct number of zeros 173 for(int j = 0; j < 4 - hexString.length(); j++) 174 { 175 buffer.append('0'); 176 } 177 buffer.append(hexString.toUpperCase()); 178 } 179 else 180 { 181 // No escaping necessary 182 buffer.append(theChar); 183 } 184 } 185 186 } 187 188 escapedValue = buffer.toString(); 189 } 190 191 return escapedValue; 192 } 193 194 //-------------------------------------------------------------------------- 195 /** 196 * Un-escapes JSON string values according to <a href='http://www.ietf.org/rfc/rfc4627.txt'>RFC 4627</a>. 197 * @param inValue the String to be unescaped 198 * @return the unescaped String value 199 */ 200 public static String unescapeString(String inValue) 201 { 202 String unescapedString = null; 203 204 if (inValue != null) 205 { 206 unescapedString = inValue.replace("\\\"", "\""); 207 unescapedString = unescapedString.replace("\\\'", "'"); 208 unescapedString = unescapedString.replace("\\r", "\r"); 209 unescapedString = unescapedString.replace("\\n", "\n"); 210 unescapedString = unescapedString.replace("\\t", "\t"); 211 unescapedString = unescapedString.replace("\\\\", "\\"); 212 213 //TODO: isocontrol characters 214 } 215 216 return unescapedString; 217 } 218 219 //-------------------------------------------------------------------------- 220 /** 221 * This function strips any line comments from the JSON since they are not valid JSON syntax. 222 * @param inJSON the JSON string to be cleansed of line comments 223 * @return the cleansed JSON string value 224 */ 225 public static CharSequence stripLineComments(CharSequence inJSON) 226 { 227 String[] lines = StringUtil.lines(inJSON); 228 229 boolean commentsRemoved = false; 230 231 for (int lineIndex = 0; lineIndex < lines.length; lineIndex++) 232 { 233 String line = lines[lineIndex]; 234 235 int startingIndex = 0; 236 int index; 237 boolean inQuotes = false; 238 while (startingIndex < line.length() 239 && (index = line.indexOf("//", startingIndex)) >= 0) 240 { 241 // We can't simply truncate from the '//' because they could be contained within quotes. 242 243 char prevChar = ' '; 244 for (int i = startingIndex; i < index; i++) 245 { 246 char theChar = line.charAt(i); 247 248 if (line.charAt(i) == '"' 249 && prevChar != '\\') // Make sure it isn't an escaped quote 250 { 251 inQuotes = !inQuotes; 252 } 253 254 prevChar = theChar; 255 } 256 257 if (! inQuotes) 258 { 259 lines[lineIndex] = line.substring(0, index); 260 commentsRemoved = true; 261 break; 262 } 263 264 startingIndex = index + 2; 265 } 266 } 267 268 return commentsRemoved ? StringUtil.join(lines, "\n") : inJSON; 269 } 270 271 //-------------------------------------------------------------------------- 272 protected static Object convertStringValueToObject(String inStringValue) 273 { 274 Object valueObj; 275 try 276 { 277 if (inStringValue.equals("null") 278 || inStringValue.equals("undefined")) // Coming from javascript, it may be 'undefined' 279 { 280 valueObj = null; 281 } 282 else if (inStringValue.equals("true")) 283 { 284 valueObj = Boolean.TRUE; 285 } 286 else if (inStringValue.equals("false")) 287 { 288 valueObj = Boolean.FALSE; 289 } 290 else if (inStringValue.indexOf(".") > 0) 291 { 292 valueObj = Double.parseDouble(inStringValue); 293 } 294 else 295 { 296 valueObj = Integer.parseInt(inStringValue); 297 } 298 } 299 catch (Exception e) 300 { 301 valueObj = inStringValue; 302 } 303 304 return valueObj; 305 } 306}