001/* Copyright 2016 Clifton Labs 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * http://www.apache.org/licenses/LICENSE-2.0 006 * Unless required by applicable law or agreed to in writing, software 007 * distributed under the License is distributed on an "AS IS" BASIS, 008 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 009 * See the License for the specific language governing permissions and 010 * limitations under the License. */ 011package org.json.simple; 012 013import java.io.IOException; 014import java.io.Reader; 015import java.io.StringReader; 016import java.io.StringWriter; 017import java.io.Writer; 018import java.util.Collection; 019import java.util.EnumSet; 020import java.util.Iterator; 021import java.util.LinkedList; 022import java.util.Map; 023import java.util.Set; 024 025/** Jsoner provides JSON utilities for escaping strings to be JSON compatible, thread safe parsing (RFC 4627) JSON 026 * strings, and serializing data to strings in JSON format. 027 * @since 2.0.0 */ 028public class Jsoner{ 029 /** Flags to tweak the behavior of the primary deserialization method. */ 030 private static enum DeserializationOptions{ 031 /** Whether multiple JSON values can be deserialized as a root element. */ 032 ALLOW_CONCATENATED_JSON_VALUES, 033 /** Whether a JsonArray can be deserialized as a root element. */ 034 ALLOW_JSON_ARRAYS, 035 /** Whether a boolean, null, Number, or String can be deserialized as a root element. */ 036 ALLOW_JSON_DATA, 037 /** Whether a JsonObject can be deserialized as a root element. */ 038 ALLOW_JSON_OBJECTS; 039 } 040 041 /** Flags to tweak the behavior of the primary serialization method. */ 042 private static enum SerializationOptions{ 043 /** Instead of aborting serialization on non-JSON values that are Enums it will continue serialization with the 044 * Enums' "${PACKAGE}.${DECLARING_CLASS}.${NAME}". 045 * @see Enum 046 * @deprecated 2.3.0 the enum should implement Jsonable instead. */ 047 @Deprecated 048 ALLOW_FULLY_QUALIFIED_ENUMERATIONS, 049 /** Instead of aborting serialization on non-JSON values it will continue serialization by serializing the 050 * non-JSON value directly into the now invalid JSON. Be mindful that invalid JSON will not successfully 051 * deserialize. */ 052 ALLOW_INVALIDS, 053 /** Instead of aborting serialization on non-JSON values that implement Jsonable it will continue serialization 054 * by deferring serialization to the Jsonable. 055 * @see Jsonable */ 056 ALLOW_JSONABLES, 057 /** Instead of aborting serialization on non-JSON values it will continue serialization by using reflection to 058 * best describe the value as a JsonObject. 059 * @deprecated 2.3.0 there is no passive way to accomplish this contract and so will be abandoned. */ 060 @Deprecated 061 ALLOW_UNDEFINEDS; 062 } 063 064 /** The possible States of a JSON deserializer. */ 065 private static enum States{ 066 /** Post-parsing state. */ 067 DONE, 068 /** Pre-parsing state. */ 069 INITIAL, 070 /** Parsing error, ParsingException should be thrown. */ 071 PARSED_ERROR, 072 PARSING_ARRAY, 073 /** Parsing a key-value pair inside of an object. */ 074 PARSING_ENTRY, 075 PARSING_OBJECT; 076 } 077 078 private Jsoner(){ 079 /* Keeping it classy. */ 080 } 081 082 /** Deserializes a readable stream according to the RFC 4627 JSON specification. 083 * @param readableDeserializable representing content to be deserialized as JSON. 084 * @return either a boolean, null, Number, String, JsonObject, or JsonArray that best represents the deserializable. 085 * @throws DeserializationException if an unexpected token is encountered in the deserializable. To recover from a 086 * DeserializationException: fix the deserializable 087 * to no longer have an unexpected token and try again. */ 088 public static Object deserialize(final Reader readableDeserializable) throws DeserializationException{ 089 return Jsoner.deserialize(readableDeserializable, EnumSet.of(DeserializationOptions.ALLOW_JSON_ARRAYS, DeserializationOptions.ALLOW_JSON_OBJECTS, DeserializationOptions.ALLOW_JSON_DATA)).get(0); 090 } 091 092 /** Deserialize a stream with all deserialized JSON values are wrapped in a JsonArray. 093 * @param deserializable representing content to be deserialized as JSON. 094 * @param flags representing the allowances and restrictions on deserialization. 095 * @return the allowable object best represented by the deserializable. 096 * @throws DeserializationException if a disallowed or unexpected token is encountered in the deserializable. To 097 * recover from a DeserializationException: fix the 098 * deserializable to no longer have a disallowed or unexpected token and try again. */ 099 private static JsonArray deserialize(final Reader deserializable, final Set<DeserializationOptions> flags) throws DeserializationException{ 100 final Yylex lexer = new Yylex(deserializable); 101 Yytoken token; 102 States currentState; 103 int returnCount = 1; 104 final LinkedList<States> stateStack = new LinkedList<>(); 105 final LinkedList<Object> valueStack = new LinkedList<>(); 106 stateStack.addLast(States.INITIAL); 107 //System.out.println("//////////DESERIALIZING//////////"); 108 do{ 109 /* Parse through the parsable string's tokens. */ 110 currentState = Jsoner.popNextState(stateStack); 111 token = Jsoner.lexNextToken(lexer); 112 switch(currentState){ 113 case DONE: 114 /* The parse has finished a JSON value. */ 115 if(!flags.contains(DeserializationOptions.ALLOW_CONCATENATED_JSON_VALUES) || Yytoken.Types.END.equals(token.getType())){ 116 /* Break if concatenated values are not allowed or if an END token is read. */ 117 break; 118 } 119 /* Increment the amount of returned JSON values and treat the token as if it were a fresh parse. */ 120 returnCount += 1; 121 /* Fall through to the case for the initial state. */ 122 //$FALL-THROUGH$ 123 case INITIAL: 124 /* The parse has just started. */ 125 switch(token.getType()){ 126 case DATUM: 127 /* A boolean, null, Number, or String could be detected. */ 128 if(flags.contains(DeserializationOptions.ALLOW_JSON_DATA)){ 129 valueStack.addLast(token.getValue()); 130 stateStack.addLast(States.DONE); 131 }else{ 132 throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.DISALLOWED_TOKEN, token); 133 } 134 break; 135 case LEFT_BRACE: 136 /* An object is detected. */ 137 if(flags.contains(DeserializationOptions.ALLOW_JSON_OBJECTS)){ 138 valueStack.addLast(new JsonObject()); 139 stateStack.addLast(States.PARSING_OBJECT); 140 }else{ 141 throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.DISALLOWED_TOKEN, token); 142 } 143 break; 144 case LEFT_SQUARE: 145 /* An array is detected. */ 146 if(flags.contains(DeserializationOptions.ALLOW_JSON_ARRAYS)){ 147 valueStack.addLast(new JsonArray()); 148 stateStack.addLast(States.PARSING_ARRAY); 149 }else{ 150 throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.DISALLOWED_TOKEN, token); 151 } 152 break; 153 default: 154 /* Neither a JSON array or object was detected. */ 155 throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); 156 } 157 break; 158 case PARSED_ERROR: 159 /* The parse could be in this state due to the state stack not having a state to pop off. */ 160 throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); 161 case PARSING_ARRAY: 162 switch(token.getType()){ 163 case COMMA: 164 /* The parse could detect a comma while parsing an array since it separates each element. */ 165 stateStack.addLast(currentState); 166 break; 167 case DATUM: 168 /* The parse found an element of the array. */ 169 JsonArray val = (JsonArray)valueStack.getLast(); 170 val.add(token.getValue()); 171 stateStack.addLast(currentState); 172 break; 173 case LEFT_BRACE: 174 /* The parse found an object in the array. */ 175 val = (JsonArray)valueStack.getLast(); 176 final JsonObject object = new JsonObject(); 177 val.add(object); 178 valueStack.addLast(object); 179 stateStack.addLast(currentState); 180 stateStack.addLast(States.PARSING_OBJECT); 181 break; 182 case LEFT_SQUARE: 183 /* The parse found another array in the array. */ 184 val = (JsonArray)valueStack.getLast(); 185 final JsonArray array = new JsonArray(); 186 val.add(array); 187 valueStack.addLast(array); 188 stateStack.addLast(currentState); 189 stateStack.addLast(States.PARSING_ARRAY); 190 break; 191 case RIGHT_SQUARE: 192 /* The parse found the end of the array. */ 193 if(valueStack.size() > returnCount){ 194 valueStack.removeLast(); 195 }else{ 196 /* The parse has been fully resolved. */ 197 stateStack.addLast(States.DONE); 198 } 199 break; 200 default: 201 /* Any other token is invalid in an array. */ 202 throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); 203 } 204 break; 205 case PARSING_OBJECT: 206 /* The parse has detected the start of an object. */ 207 switch(token.getType()){ 208 case COMMA: 209 /* The parse could detect a comma while parsing an object since it separates each key value 210 * pair. Continue parsing the object. */ 211 stateStack.addLast(currentState); 212 break; 213 case DATUM: 214 /* The token ought to be a key. */ 215 if(token.getValue() instanceof String){ 216 /* JSON keys are always strings, strings are not always JSON keys but it is going to be 217 * treated as one. Continue parsing the object. */ 218 final String key = (String)token.getValue(); 219 valueStack.addLast(key); 220 stateStack.addLast(currentState); 221 stateStack.addLast(States.PARSING_ENTRY); 222 }else{ 223 /* Abort! JSON keys are always strings and it wasn't a string. */ 224 throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); 225 } 226 break; 227 case RIGHT_BRACE: 228 /* The parse has found the end of the object. */ 229 if(valueStack.size() > returnCount){ 230 /* There are unresolved values remaining. */ 231 valueStack.removeLast(); 232 }else{ 233 /* The parse has been fully resolved. */ 234 stateStack.addLast(States.DONE); 235 } 236 break; 237 default: 238 /* The parse didn't detect the end of an object or a key. */ 239 throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); 240 } 241 break; 242 case PARSING_ENTRY: 243 switch(token.getType()){ 244 /* Parsed pair keys can only happen while parsing objects. */ 245 case COLON: 246 /* The parse could detect a colon while parsing a key value pair since it separates the key 247 * and value from each other. Continue parsing the entry. */ 248 stateStack.addLast(currentState); 249 break; 250 case DATUM: 251 /* The parse has found a value for the parsed pair key. */ 252 String key = (String)valueStack.removeLast(); 253 JsonObject parent = (JsonObject)valueStack.getLast(); 254 parent.put(key, token.getValue()); 255 break; 256 case LEFT_BRACE: 257 /* The parse has found an object for the parsed pair key. */ 258 key = (String)valueStack.removeLast(); 259 parent = (JsonObject)valueStack.getLast(); 260 final JsonObject object = new JsonObject(); 261 parent.put(key, object); 262 valueStack.addLast(object); 263 stateStack.addLast(States.PARSING_OBJECT); 264 break; 265 case LEFT_SQUARE: 266 /* The parse has found an array for the parsed pair key. */ 267 key = (String)valueStack.removeLast(); 268 parent = (JsonObject)valueStack.getLast(); 269 final JsonArray array = new JsonArray(); 270 parent.put(key, array); 271 valueStack.addLast(array); 272 stateStack.addLast(States.PARSING_ARRAY); 273 break; 274 default: 275 /* The parse didn't find anything for the parsed pair key. */ 276 throw new DeserializationException(lexer.getPosition(), DeserializationException.Problems.UNEXPECTED_TOKEN, token); 277 } 278 break; 279 default: 280 break; 281 } 282 //System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~"); 283 //System.out.println(currentState); 284 //System.out.println(token); 285 //System.out.println(valueStack); 286 //System.out.println(stateStack); 287 /* If we're not at the END and DONE then do the above again. */ 288 }while(!(States.DONE.equals(currentState) && Yytoken.Types.END.equals(token.getType()))); 289 //System.out.println("!!!!!!!!!!DESERIALIZED!!!!!!!!!!"); 290 return new JsonArray(valueStack); 291 } 292 293 /** A convenience method that assumes a StringReader to deserialize a string. 294 * @param deserializable representing content to be deserialized as JSON. 295 * @return either a boolean, null, Number, String, JsonObject, or JsonArray that best represents the deserializable. 296 * @throws DeserializationException if an unexpected token is encountered in the deserializable. To recover from a 297 * DeserializationException: fix the deserializable 298 * to no longer have an unexpected token and try again. 299 * @see Jsoner#deserialize(Reader) 300 * @see StringReader */ 301 public static Object deserialize(final String deserializable) throws DeserializationException{ 302 Object returnable; 303 StringReader readableDeserializable = null; 304 try{ 305 readableDeserializable = new StringReader(deserializable); 306 returnable = Jsoner.deserialize(readableDeserializable); 307 }catch(final NullPointerException caught){ 308 /* They both have the same recovery scenario. 309 * See StringReader. 310 * If deserializable is null, it should be reasonable to expect null back. */ 311 returnable = null; 312 }finally{ 313 if(readableDeserializable != null){ 314 readableDeserializable.close(); 315 } 316 } 317 return returnable; 318 } 319 320 /** A convenience method that assumes a JsonArray must be deserialized. 321 * @param deserializable representing content to be deserializable as a JsonArray. 322 * @param defaultValue representing what would be returned if deserializable isn't a JsonArray or an IOException, 323 * NullPointerException, or DeserializationException occurs during deserialization. 324 * @return a JsonArray that represents the deserializable, or the defaultValue if there isn't a JsonArray that 325 * represents deserializable. 326 * @see Jsoner#deserialize(Reader) */ 327 public static JsonArray deserialize(final String deserializable, final JsonArray defaultValue){ 328 StringReader readable = null; 329 JsonArray returnable; 330 try{ 331 readable = new StringReader(deserializable); 332 returnable = Jsoner.deserialize(readable, EnumSet.of(DeserializationOptions.ALLOW_JSON_ARRAYS)).<JsonArray> getCollection(0); 333 }catch(NullPointerException | DeserializationException caught){ 334 /* Don't care, just return the default value. */ 335 returnable = defaultValue; 336 }finally{ 337 if(readable != null){ 338 readable.close(); 339 } 340 } 341 return returnable; 342 } 343 344 /** A convenience method that assumes a JsonObject must be deserialized. 345 * @param deserializable representing content to be deserializable as a JsonObject. 346 * @param defaultValue representing what would be returned if deserializable isn't a JsonObject or an IOException, 347 * NullPointerException, or DeserializationException occurs during deserialization. 348 * @return a JsonObject that represents the deserializable, or the defaultValue if there isn't a JsonObject that 349 * represents deserializable. 350 * @see Jsoner#deserialize(Reader) */ 351 public static JsonObject deserialize(final String deserializable, final JsonObject defaultValue){ 352 StringReader readable = null; 353 JsonObject returnable; 354 try{ 355 readable = new StringReader(deserializable); 356 returnable = Jsoner.deserialize(readable, EnumSet.of(DeserializationOptions.ALLOW_JSON_OBJECTS)).<JsonObject> getMap(0); 357 }catch(NullPointerException | DeserializationException caught){ 358 /* Don't care, just return the default value. */ 359 returnable = defaultValue; 360 }finally{ 361 if(readable != null){ 362 readable.close(); 363 } 364 } 365 return returnable; 366 } 367 368 /** A convenience method that assumes multiple RFC 4627 JSON values (except numbers) have been concatenated together 369 * for deserilization which will be collectively returned in a JsonArray wrapper. 370 * There may be numbers included, they just must not be concatenated together as it is prone to 371 * NumberFormatExceptions (thus causing a DeserializationException) or the numbers no longer represent their 372 * respective values. 373 * Examples: 374 * "123null321" returns [123, null, 321] 375 * "nullnullnulltruefalse\"\"{}[]" returns [null, null, null, true, false, "", {}, []] 376 * "123" appended to "321" returns [123321] 377 * "12.3" appended to "3.21" throws DeserializationException(NumberFormatException) 378 * "123" appended to "-321" throws DeserializationException(NumberFormatException) 379 * "123e321" appended to "-1" throws DeserializationException(NumberFormatException) 380 * "null12.33.21null" throws DeserializationException(NumberFormatException) 381 * @param deserializable representing concatenated content to be deserialized as JSON in one reader. Its contents 382 * may 383 * not contain two numbers concatenated together. 384 * @return a JsonArray that contains each of the concatenated objects as its elements. Each concatenated element is 385 * either a boolean, null, Number, String, JsonArray, or JsonObject that best represents the concatenated 386 * content inside deserializable. 387 * @throws DeserializationException if an unexpected token is encountered in the deserializable. To recover from a 388 * DeserializationException: fix the deserializable to no longer have an unexpected token and try again. */ 389 public static JsonArray deserializeMany(final Reader deserializable) throws DeserializationException{ 390 return Jsoner.deserialize(deserializable, EnumSet.of(DeserializationOptions.ALLOW_JSON_ARRAYS, DeserializationOptions.ALLOW_JSON_OBJECTS, DeserializationOptions.ALLOW_JSON_DATA, DeserializationOptions.ALLOW_CONCATENATED_JSON_VALUES)); 391 } 392 393 /** Escapes potentially confusing or important characters in the String provided. 394 * @param escapable an unescaped string. 395 * @return an escaped string for usage in JSON; An escaped string is one that has escaped all of the quotes ("), 396 * backslashes (\), return character (\r), new line character (\n), tab character (\t), 397 * backspace character (\b), form feed character (\f) and other control characters [u0000..u001F] or 398 * characters [u007F..u009F], [u2000..u20FF] with a 399 * backslash (\) which itself must be escaped by the backslash in a java string. */ 400 public static String escape(final String escapable){ 401 final StringBuilder builder = new StringBuilder(); 402 final int characters = escapable.length(); 403 for(int i = 0; i < characters; i++){ 404 final char character = escapable.charAt(i); 405 switch(character){ 406 case '"': 407 builder.append("\\\""); 408 break; 409 case '\\': 410 builder.append("\\\\"); 411 break; 412 case '\b': 413 builder.append("\\b"); 414 break; 415 case '\f': 416 builder.append("\\f"); 417 break; 418 case '\n': 419 builder.append("\\n"); 420 break; 421 case '\r': 422 builder.append("\\r"); 423 break; 424 case '\t': 425 builder.append("\\t"); 426 break; 427 case '/': 428 builder.append("\\/"); 429 break; 430 default: 431 /* The many characters that get replaced are benign to software but could be mistaken by people 432 * reading it for a JSON relevant character. */ 433 if(((character >= '\u0000') && (character <= '\u001F')) || ((character >= '\u007F') && (character <= '\u009F')) || ((character >= '\u2000') && (character <= '\u20FF'))){ 434 final String characterHexCode = Integer.toHexString(character); 435 builder.append("\\u"); 436 for(int k = 0; k < (4 - characterHexCode.length()); k++){ 437 builder.append("0"); 438 } 439 builder.append(characterHexCode.toUpperCase()); 440 }else{ 441 /* Character didn't need escaping. */ 442 builder.append(character); 443 } 444 } 445 } 446 return builder.toString(); 447 } 448 449 /** Processes the lexer's reader for the next token. 450 * @param lexer represents a text processor being used in the deserialization process. 451 * @return a token representing a meaningful element encountered by the lexer. 452 * @throws DeserializationException if an unexpected character is encountered while processing the text. */ 453 private static Yytoken lexNextToken(final Yylex lexer) throws DeserializationException{ 454 Yytoken returnable; 455 /* Parse through the next token. */ 456 try{ 457 returnable = lexer.yylex(); 458 }catch(final IOException caught){ 459 throw new DeserializationException(-1, DeserializationException.Problems.UNEXPECTED_EXCEPTION, caught); 460 } 461 if(returnable == null){ 462 /* If there isn't another token, it must be the end. */ 463 returnable = new Yytoken(Yytoken.Types.END, null); 464 } 465 return returnable; 466 } 467 468 /** Creates a new JsonKey that wraps the given string and value. This function should NOT be 469 * used in favor of existing constants and enumerations to make code easier to maintain. 470 * @param key represents the JsonKey as a String. 471 * @param value represents the value the JsonKey uses. 472 * @return a JsonKey that represents the provided key and value. */ 473 public static JsonKey mintJsonKey(final String key, final Object value){ 474 return new JsonKey(){ 475 @Override 476 public String getKey(){ 477 return key; 478 } 479 480 @Override 481 public Object getValue(){ 482 return value; 483 } 484 }; 485 } 486 487 /** Used for state transitions while deserializing. 488 * @param stateStack represents the deserialization states saved for future processing. 489 * @return a state for deserialization context so it knows how to consume the next token. */ 490 private static States popNextState(final LinkedList<States> stateStack){ 491 if(stateStack.size() > 0){ 492 return stateStack.removeLast(); 493 }else{ 494 return States.PARSED_ERROR; 495 } 496 } 497 498 /** Formats the JSON string to be more easily human readable using tabs for indentation. 499 * @param printable representing a JSON formatted string with out extraneous characters, like one returned from 500 * Jsoner#serialize(Object). 501 * @return printable except it will have '\n' then '\t' characters inserted after '[', '{', ',' and before ']' '}' 502 * tokens in the JSON. It will return null if printable isn't a JSON string. */ 503 public static String prettyPrint(final String printable){ 504 return Jsoner.prettyPrint(printable, "\t"); 505 } 506 507 /** Formats the JSON string to be more easily human readable using an arbitrary amount of spaces for indentation. 508 * @param printable representing a JSON formatted string with out extraneous characters, like one returned from 509 * Jsoner#serialize(Object). 510 * @param spaces representing the amount of spaces to use for indentation. Must be between 2 and 10. 511 * @return printable except it will have '\n' then space characters inserted after '[', '{', ',' and before ']' '}' 512 * tokens in the JSON. It will return null if printable isn't a JSON string. 513 * @throws IllegalArgumentException if spaces isn't between [2..10]. 514 * @see Jsoner#prettyPrint(String) 515 * @since 2.2.0 to allow pretty printing with spaces instead of tabs. */ 516 public static String prettyPrint(final String printable, final int spaces){ 517 if((spaces > 10) || (spaces < 2)){ 518 throw new IllegalArgumentException("Indentation with spaces must be between 2 and 10."); 519 } 520 final StringBuilder indentation = new StringBuilder(""); 521 for(int i = 0; i < spaces; i++){ 522 indentation.append(" "); 523 } 524 return Jsoner.prettyPrint(printable, indentation.toString()); 525 } 526 527 /** Makes the JSON string more easily human readable using indentation of the caller's choice. 528 * @param printable representing a JSON formatted string with out extraneous characters, like one returned from 529 * Jsoner#serialize(Object). 530 * @param indentation representing the indentation used to format the JSON string. 531 * @return printable except it will have '\n' then indentation characters inserted after '[', '{', ',' and before 532 * ']' '}' 533 * tokens in the JSON. It will return null if printable isn't a JSON string. */ 534 private static String prettyPrint(final String printable, final String indentation){ 535 final Yylex lexer = new Yylex(new StringReader(printable)); 536 Yytoken lexed; 537 final StringBuilder returnable = new StringBuilder(); 538 int level = 0; 539 try{ 540 do{ 541 lexed = Jsoner.lexNextToken(lexer); 542 switch(lexed.getType()){ 543 case COLON: 544 returnable.append(":"); 545 break; 546 case COMMA: 547 returnable.append(lexed.getValue()); 548 returnable.append("\n"); 549 for(int i = 0; i < level; i++){ 550 returnable.append(indentation); 551 } 552 break; 553 case END: 554 break; 555 case LEFT_BRACE: 556 case LEFT_SQUARE: 557 returnable.append(lexed.getValue()); 558 returnable.append("\n"); 559 level++; 560 for(int i = 0; i < level; i++){ 561 returnable.append(indentation); 562 } 563 break; 564 case RIGHT_BRACE: 565 case RIGHT_SQUARE: 566 returnable.append("\n"); 567 level--; 568 for(int i = 0; i < level; i++){ 569 returnable.append(indentation); 570 } 571 returnable.append(lexed.getValue()); 572 break; 573 default: 574 if(lexed.getValue() instanceof String){ 575 returnable.append("\""); 576 returnable.append(Jsoner.escape((String)lexed.getValue())); 577 returnable.append("\""); 578 }else{ 579 returnable.append(lexed.getValue()); 580 } 581 break; 582 } 583 //System.out.println(lexed); 584 }while(!lexed.getType().equals(Yytoken.Types.END)); 585 }catch(final DeserializationException caught){ 586 /* This is according to the method's contract. */ 587 return null; 588 } 589 //System.out.println(printable); 590 //System.out.println(returnable); 591 //System.out.println(Jsoner.escape(returnable.toString())); 592 return returnable.toString(); 593 } 594 595 /** A convenience method that assumes a StringWriter. 596 * @param jsonSerializable represents the object that should be serialized as a string in JSON format. 597 * @return a string, in JSON format, that represents the object provided. 598 * @throws IllegalArgumentException if the jsonSerializable isn't serializable in JSON. 599 * @see Jsoner#serialize(Object, Writer) 600 * @see StringWriter */ 601 public static String serialize(final Object jsonSerializable){ 602 final StringWriter writableDestination = new StringWriter(); 603 try{ 604 Jsoner.serialize(jsonSerializable, writableDestination); 605 }catch(final IOException caught){ 606 /* See StringWriter. */ 607 } 608 return writableDestination.toString(); 609 } 610 611 /** Serializes values according to the RFC 4627 JSON specification. It will also trust the serialization provided by 612 * any Jsonables it serializes and serializes Enums that don't implement Jsonable as a string of their fully 613 * qualified name. 614 * @param jsonSerializable represents the object that should be serialized in JSON format. 615 * @param writableDestination represents where the resulting JSON text is written to. 616 * @throws IOException if the writableDestination encounters an I/O problem, like being closed while in use. 617 * @throws IllegalArgumentException if the jsonSerializable isn't serializable in JSON. */ 618 public static void serialize(final Object jsonSerializable, final Writer writableDestination) throws IOException{ 619 Jsoner.serialize(jsonSerializable, writableDestination, EnumSet.of(SerializationOptions.ALLOW_JSONABLES, SerializationOptions.ALLOW_FULLY_QUALIFIED_ENUMERATIONS)); 620 } 621 622 /** Serialize values to JSON and write them to the provided writer based on behavior flags. 623 * @param jsonSerializable represents the object that should be serialized to a string in JSON format. 624 * @param writableDestination represents where the resulting JSON text is written to. 625 * @param replacement represents what is serialized instead of a non-JSON value when replacements are allowed. 626 * @param flags represents the allowances and restrictions on serialization. 627 * @throws IOException if the writableDestination encounters an I/O problem. 628 * @throws IllegalArgumentException if the jsonSerializable isn't serializable in JSON. 629 * @see SerializationOptions */ 630 private static void serialize(final Object jsonSerializable, final Writer writableDestination, final Set<SerializationOptions> flags) throws IOException{ 631 if(jsonSerializable == null){ 632 /* When a null is passed in the word null is supported in JSON. */ 633 writableDestination.write("null"); 634 }else if(((jsonSerializable instanceof Jsonable) && flags.contains(SerializationOptions.ALLOW_JSONABLES))){ 635 /* Writes the writable as defined by the writable. */ 636 writableDestination.write(((Jsonable)jsonSerializable).toJson()); 637 }else if((jsonSerializable instanceof Enum) && flags.contains(SerializationOptions.ALLOW_FULLY_QUALIFIED_ENUMERATIONS)){ 638 /* Writes the enum as a special case of string. All enums (unless they implement Jsonable) will be the 639 * string literal "${DECLARING_CLASS_NAME}.${ENUM_NAME}" as their value. */ 640 @SuppressWarnings("rawtypes") 641 final Enum e = (Enum)jsonSerializable; 642 writableDestination.write('"'); 643 writableDestination.write(e.getDeclaringClass().getName()); 644 writableDestination.write('.'); 645 writableDestination.write(e.name()); 646 writableDestination.write('"'); 647 }else if(jsonSerializable instanceof String){ 648 /* Make sure the string is properly escaped. */ 649 writableDestination.write('"'); 650 writableDestination.write(Jsoner.escape((String)jsonSerializable)); 651 writableDestination.write('"'); 652 }else if(jsonSerializable instanceof Character){ 653 /* Make sure the string is properly escaped. */ 654 //writableDestination.write('"'); 655 writableDestination.write(Jsoner.escape(jsonSerializable.toString())); 656 //writableDestination.write('"'); 657 }else if(jsonSerializable instanceof Double){ 658 if(((Double)jsonSerializable).isInfinite() || ((Double)jsonSerializable).isNaN()){ 659 /* Infinite and not a number are not supported by the JSON specification, so null is used instead. */ 660 writableDestination.write("null"); 661 }else{ 662 writableDestination.write(jsonSerializable.toString()); 663 } 664 }else if(jsonSerializable instanceof Float){ 665 if(((Float)jsonSerializable).isInfinite() || ((Float)jsonSerializable).isNaN()){ 666 /* Infinite and not a number are not supported by the JSON specification, so null is used instead. */ 667 writableDestination.write("null"); 668 }else{ 669 writableDestination.write(jsonSerializable.toString()); 670 } 671 }else if(jsonSerializable instanceof Number){ 672 writableDestination.write(jsonSerializable.toString()); 673 }else if(jsonSerializable instanceof Boolean){ 674 writableDestination.write(jsonSerializable.toString()); 675 }else if(jsonSerializable instanceof Map){ 676 /* Writes the map in JSON object format. */ 677 boolean isFirstEntry = true; 678 @SuppressWarnings("rawtypes") 679 final Iterator entries = ((Map)jsonSerializable).entrySet().iterator(); 680 writableDestination.write('{'); 681 while(entries.hasNext()){ 682 if(isFirstEntry){ 683 isFirstEntry = false; 684 }else{ 685 writableDestination.write(','); 686 } 687 @SuppressWarnings("rawtypes") 688 final Map.Entry entry = (Map.Entry)entries.next(); 689 Jsoner.serialize(entry.getKey(), writableDestination, flags); 690 writableDestination.write(':'); 691 Jsoner.serialize(entry.getValue(), writableDestination, flags); 692 } 693 writableDestination.write('}'); 694 }else if(jsonSerializable instanceof Collection){ 695 /* Writes the collection in JSON array format. */ 696 boolean isFirstElement = true; 697 @SuppressWarnings("rawtypes") 698 final Iterator elements = ((Collection)jsonSerializable).iterator(); 699 writableDestination.write('['); 700 while(elements.hasNext()){ 701 if(isFirstElement){ 702 isFirstElement = false; 703 }else{ 704 writableDestination.write(','); 705 } 706 Jsoner.serialize(elements.next(), writableDestination, flags); 707 } 708 writableDestination.write(']'); 709 }else if(jsonSerializable instanceof byte[]){ 710 /* Writes the array in JSON array format. */ 711 final byte[] writableArray = (byte[])jsonSerializable; 712 final int numberOfElements = writableArray.length; 713 writableDestination.write('['); 714 for(int i = 0; i < numberOfElements; i++){ 715 if(i == (numberOfElements - 1)){ 716 Jsoner.serialize(writableArray[i], writableDestination, flags); 717 }else{ 718 Jsoner.serialize(writableArray[i], writableDestination, flags); 719 writableDestination.write(','); 720 } 721 } 722 writableDestination.write(']'); 723 }else if(jsonSerializable instanceof short[]){ 724 /* Writes the array in JSON array format. */ 725 final short[] writableArray = (short[])jsonSerializable; 726 final int numberOfElements = writableArray.length; 727 writableDestination.write('['); 728 for(int i = 0; i < numberOfElements; i++){ 729 if(i == (numberOfElements - 1)){ 730 Jsoner.serialize(writableArray[i], writableDestination, flags); 731 }else{ 732 Jsoner.serialize(writableArray[i], writableDestination, flags); 733 writableDestination.write(','); 734 } 735 } 736 writableDestination.write(']'); 737 }else if(jsonSerializable instanceof int[]){ 738 /* Writes the array in JSON array format. */ 739 final int[] writableArray = (int[])jsonSerializable; 740 final int numberOfElements = writableArray.length; 741 writableDestination.write('['); 742 for(int i = 0; i < numberOfElements; i++){ 743 if(i == (numberOfElements - 1)){ 744 Jsoner.serialize(writableArray[i], writableDestination, flags); 745 }else{ 746 Jsoner.serialize(writableArray[i], writableDestination, flags); 747 writableDestination.write(','); 748 } 749 } 750 writableDestination.write(']'); 751 }else if(jsonSerializable instanceof long[]){ 752 /* Writes the array in JSON array format. */ 753 final long[] writableArray = (long[])jsonSerializable; 754 final int numberOfElements = writableArray.length; 755 writableDestination.write('['); 756 for(int i = 0; i < numberOfElements; i++){ 757 if(i == (numberOfElements - 1)){ 758 Jsoner.serialize(writableArray[i], writableDestination, flags); 759 }else{ 760 Jsoner.serialize(writableArray[i], writableDestination, flags); 761 writableDestination.write(','); 762 } 763 } 764 writableDestination.write(']'); 765 }else if(jsonSerializable instanceof float[]){ 766 /* Writes the array in JSON array format. */ 767 final float[] writableArray = (float[])jsonSerializable; 768 final int numberOfElements = writableArray.length; 769 writableDestination.write('['); 770 for(int i = 0; i < numberOfElements; i++){ 771 if(i == (numberOfElements - 1)){ 772 Jsoner.serialize(writableArray[i], writableDestination, flags); 773 }else{ 774 Jsoner.serialize(writableArray[i], writableDestination, flags); 775 writableDestination.write(','); 776 } 777 } 778 writableDestination.write(']'); 779 }else if(jsonSerializable instanceof double[]){ 780 /* Writes the array in JSON array format. */ 781 final double[] writableArray = (double[])jsonSerializable; 782 final int numberOfElements = writableArray.length; 783 writableDestination.write('['); 784 for(int i = 0; i < numberOfElements; i++){ 785 if(i == (numberOfElements - 1)){ 786 Jsoner.serialize(writableArray[i], writableDestination, flags); 787 }else{ 788 Jsoner.serialize(writableArray[i], writableDestination, flags); 789 writableDestination.write(','); 790 } 791 } 792 writableDestination.write(']'); 793 }else if(jsonSerializable instanceof boolean[]){ 794 /* Writes the array in JSON array format. */ 795 final boolean[] writableArray = (boolean[])jsonSerializable; 796 final int numberOfElements = writableArray.length; 797 writableDestination.write('['); 798 for(int i = 0; i < numberOfElements; i++){ 799 if(i == (numberOfElements - 1)){ 800 Jsoner.serialize(writableArray[i], writableDestination, flags); 801 }else{ 802 Jsoner.serialize(writableArray[i], writableDestination, flags); 803 writableDestination.write(','); 804 } 805 } 806 writableDestination.write(']'); 807 }else if(jsonSerializable instanceof char[]){ 808 /* Writes the array in JSON array format. */ 809 final char[] writableArray = (char[])jsonSerializable; 810 final int numberOfElements = writableArray.length; 811 writableDestination.write("[\""); 812 for(int i = 0; i < numberOfElements; i++){ 813 if(i == (numberOfElements - 1)){ 814 Jsoner.serialize(writableArray[i], writableDestination, flags); 815 }else{ 816 Jsoner.serialize(writableArray[i], writableDestination, flags); 817 writableDestination.write("\",\""); 818 } 819 } 820 writableDestination.write("\"]"); 821 }else if(jsonSerializable instanceof Object[]){ 822 /* Writes the array in JSON array format. */ 823 final Object[] writableArray = (Object[])jsonSerializable; 824 final int numberOfElements = writableArray.length; 825 writableDestination.write('['); 826 for(int i = 0; i < numberOfElements; i++){ 827 if(i == (numberOfElements - 1)){ 828 Jsoner.serialize(writableArray[i], writableDestination, flags); 829 }else{ 830 Jsoner.serialize(writableArray[i], writableDestination, flags); 831 writableDestination.write(","); 832 } 833 } 834 writableDestination.write(']'); 835 }else{ 836 /* It cannot by any measure be safely serialized according to specification. */ 837 if(flags.contains(SerializationOptions.ALLOW_INVALIDS)){ 838 /* Can be helpful for debugging how it isn't valid. */ 839 writableDestination.write(jsonSerializable.toString()); 840 }else{ 841 /* Notify the caller the cause of failure for the serialization. */ 842 throw new IllegalArgumentException("Encountered a: " + jsonSerializable.getClass().getName() + " as: " + jsonSerializable.toString() + " that isn't JSON serializable.\n Try:\n 1) Implementing the Jsonable interface for the object to return valid JSON. If it already does it probably has a bug.\n 2) If you cannot edit the source of the object or couple it with this library consider wrapping it in a class that does implement the Jsonable interface.\n 3) Otherwise convert it to a boolean, null, number, JsonArray, JsonObject, or String value before serializing it.\n 4) If you feel it should have serialized you could use a more tolerant serialization for debugging purposes."); 843 } 844 } 845 //System.out.println(writableDestination.toString()); 846 } 847 848 /** Serializes like the first version of this library. 849 * It has been adapted to use Jsonable for serializing custom objects, but otherwise works like the old JSON string 850 * serializer. It 851 * will allow non-JSON values in its output like the old one. It can be helpful for last resort log statements and 852 * debugging errors in self generated JSON. Anything serialized using this method isn't guaranteed to be 853 * deserializable. 854 * @param jsonSerializable represents the object that should be serialized in JSON format. 855 * @param writableDestination represents where the resulting JSON text is written to. 856 * @throws IOException if the writableDestination encounters an I/O problem, like being closed while in use. */ 857 public static void serializeCarelessly(final Object jsonSerializable, final Writer writableDestination) throws IOException{ 858 Jsoner.serialize(jsonSerializable, writableDestination, EnumSet.of(SerializationOptions.ALLOW_JSONABLES, SerializationOptions.ALLOW_INVALIDS)); 859 } 860 861 /** Serializes JSON values and only JSON values according to the RFC 4627 JSON specification. 862 * @param jsonSerializable represents the object that should be serialized in JSON format. 863 * @param writableDestination represents where the resulting JSON text is written to. 864 * @throws IOException if the writableDestination encounters an I/O problem, like being closed while in use. 865 * @throws IllegalArgumentException if the jsonSerializable isn't serializable in raw JSON. */ 866 public static void serializeStrictly(final Object jsonSerializable, final Writer writableDestination) throws IOException{ 867 Jsoner.serialize(jsonSerializable, writableDestination, EnumSet.noneOf(SerializationOptions.class)); 868 } 869}