001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.json; 018 019import java.io.*; 020 021import org.apache.juneau.*; 022import org.apache.juneau.common.utils.*; 023import org.apache.juneau.internal.*; 024import org.apache.juneau.serializer.*; 025 026/** 027 * Specialized writer for serializing JSON. 028 * 029 * <h5 class='section'>Notes:</h5><ul> 030 * <li class='note'> 031 * This class is not intended for external use. 032 * </ul> 033 * 034 * <h5 class='section'>See Also:</h5><ul> 035 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JsonBasics">JSON Basics</a> 036 037 * </ul> 038 */ 039public class JsonWriter extends SerializerWriter { 040 041 private final boolean simpleAttrs, escapeSolidus; 042 043 // Characters that trigger special handling of serializing attribute values. 044 private static final AsciiSet 045 encodedChars = AsciiSet.of("\n\t\b\f\r'\"\\"), 046 encodedChars2 = AsciiSet.of("\n\t\b\f\r'\"\\/"); 047 048 private static final KeywordSet reservedWords = new KeywordSet( 049 "arguments","break","case","catch","class","const","continue","debugger","default","delete", 050 "do","else","enum","eval","export","extends","false","finally","for","function","if", 051 "implements","import","in","instanceof","interface","let","new","null","package", 052 "private","protected","public","return","static","super","switch","this","throw", 053 "true","try","typeof","var","void","while","with","undefined","yield" 054 ); 055 056 057 // Characters that represent attribute name characters that don't trigger quoting. 058 // These are actually more strict than the actual Javascript specification, but 059 // can be narrowed in the future if necessary. 060 // For example, we quote attributes that start with $ even though we don't need to. 061 private static final AsciiSet validAttrChars = AsciiSet.create().ranges("a-z","A-Z","0-9").chars("_").build(); 062 private static final AsciiSet validFirstAttrChars = AsciiSet.create().ranges("a-z","A-Z").chars("_").build(); 063 064 private final AsciiSet ec; 065 066 /** 067 * Constructor. 068 * 069 * @param out The writer being wrapped. 070 * @param useWhitespace If <jk>true</jk>, tabs and spaces will be used in output. 071 * @param maxIndent The maximum indentation level. 072 * @param escapeSolidus If <jk>true</jk>, forward slashes should be escaped in the output. 073 * @param quoteChar The quote character to use (i.e. <js>'\''</js> or <js>'"'</js>) 074 * @param simpleAttrs If <jk>true</jk>, JSON attributes will only be quoted when necessary. 075 * @param trimStrings If <jk>true</jk>, strings will be trimmed before being serialized. 076 * @param uriResolver The URI resolver for resolving URIs to absolute or root-relative form. 077 */ 078 protected JsonWriter(Writer out, boolean useWhitespace, int maxIndent, boolean escapeSolidus, char quoteChar, 079 boolean simpleAttrs, boolean trimStrings, UriResolver uriResolver) { 080 super(out, useWhitespace, maxIndent, trimStrings, quoteChar, uriResolver); 081 this.simpleAttrs = simpleAttrs; 082 this.escapeSolidus = escapeSolidus; 083 this.ec = escapeSolidus ? encodedChars2 : encodedChars; 084 } 085 086 /** 087 * Serializes the specified object as a JSON string value. 088 * 089 * @param s The object being serialized. 090 * @return This object. 091 */ 092 public JsonWriter stringValue(String s) { 093 if (s == null) 094 return this; 095 boolean doConvert = false; 096 for (int i = 0; i < s.length() && ! doConvert; i++) { 097 char c = s.charAt(i); 098 doConvert |= ec.contains(c); 099 } 100 q(); 101 if (! doConvert) { 102 w(s); 103 } else { 104 for (int i = 0; i < s.length(); i++) { 105 char c = s.charAt(i); 106 if (ec.contains(c)) { 107 if (c == '\n') 108 w('\\').w('n'); 109 else if (c == '\t') 110 w('\\').w('t'); 111 else if (c == '\b') 112 w('\\').w('b'); 113 else if (c == '\f') 114 w('\\').w('f'); 115 else if (c == quoteChar) 116 w('\\').w(quoteChar); 117 else if (c == '\\') 118 w('\\').w('\\'); 119 else if (c == '/' && escapeSolidus) 120 w('\\').w('/'); 121 else if (c != '\r') 122 w(c); 123 } else { 124 w(c); 125 } 126 } 127 } 128 q(); 129 return this; 130 } 131 132 /** 133 * Serializes the specified object as a JSON attribute name. 134 * 135 * @param s The object being serialized. 136 * @return This object. 137 */ 138 public JsonWriter attr(String s) { 139 140 if (trimStrings) 141 s = StringUtils.trim(s); 142 143 /* 144 * Converts a Java string to an acceptable JSON attribute name. If 145 * simpleAttrs is true, then quotes will only be used if the attribute 146 * name consists of only alphanumeric characters. 147 */ 148 boolean doConvert = ! simpleAttrs; // Always convert when not in lax mode. 149 150 // If the attribute is null, it must always be printed as null without quotes. 151 // Technically, this isn't part of the JSON spec, but it does allow for null key values. 152 if (s == null) { 153 s = "null"; 154 doConvert = false; 155 156 } else { 157 158 // Look for characters that would require the attribute to be quoted. 159 // All possible numbers should be caught here. 160 if (! doConvert) { 161 for (int i = 0; i < s.length() && ! doConvert; i++) { 162 char c = s.charAt(i); 163 doConvert |= ! (i == 0 ? validFirstAttrChars.contains(c) : validAttrChars.contains(c)); 164 } 165 } 166 167 // Reserved words and blanks must be quoted. 168 if (! doConvert) { 169 if (s.isEmpty() || reservedWords.contains(s)) 170 doConvert = true; 171 } 172 } 173 174 // If no conversion necessary, just print the attribute as-is. 175 if (doConvert) 176 stringValue(s); 177 else 178 w(s); 179 180 return this; 181 } 182 183 /** 184 * Appends a URI to the output. 185 * 186 * @param uri The URI to append to the output. 187 * @return This object. 188 */ 189 public SerializerWriter uriValue(Object uri) { 190 return stringValue(uriResolver.resolve(uri)); 191 } 192 193 /** 194 * Performs an indentation only if we're currently past max indentation. 195 * 196 * @param depth The current indentation depth. 197 * @return This object. 198 */ 199 public JsonWriter smi(int depth) { 200 if (depth > maxIndent) 201 super.s(); 202 return this; 203 } 204 205 /** 206 * Adds a space only if the current indentation level is below maxIndent. 207 * 208 * @param indent The number of spaces to indent. 209 * @return This object. 210 */ 211 public JsonWriter s(int indent) { 212 if (indent <= maxIndent) 213 super.s(); 214 return this; 215 } 216 217 //----------------------------------------------------------------------------------------------------------------- 218 // Overridden methods 219 //----------------------------------------------------------------------------------------------------------------- 220 221 @Override /* SerializerWriter */ 222 public JsonWriter cr(int depth) { 223 super.cr(depth); 224 return this; 225 } 226 @Override /* SerializerWriter */ 227 public JsonWriter cre(int depth) { 228 super.cre(depth); 229 return this; 230 } 231 @Override /* SerializerWriter */ 232 public JsonWriter appendln(int indent, String text) { 233 super.appendln(indent, text); 234 return this; 235 } 236 @Override /* SerializerWriter */ 237 public JsonWriter appendln(String text) { 238 super.appendln(text); 239 return this; 240 } 241 @Override /* SerializerWriter */ 242 public JsonWriter append(int indent, String text) { 243 super.append(indent, text); 244 return this; 245 } 246 @Override /* SerializerWriter */ 247 public JsonWriter append(int indent, char c) { 248 super.append(indent, c); 249 return this; 250 } 251 @Override /* SerializerWriter */ 252 public JsonWriter s() { 253 super.s(); 254 return this; 255 } 256 @Override /* SerializerWriter */ 257 public JsonWriter w(char value) { 258 super.w(value); 259 return this; 260 } 261 @Override /* SerializerWriter */ 262 public JsonWriter w(String value) { 263 super.w(value); 264 return this; 265 } 266 @Override /* SerializerWriter */ 267 public JsonWriter q() { 268 super.q(); 269 return this; 270 } 271 @Override /* SerializerWriter */ 272 public JsonWriter i(int indent) { 273 super.i(indent); 274 return this; 275 } 276 @Override /* SerializerWriter */ 277 public JsonWriter nl(int indent) { 278 super.nl(indent); 279 return this; 280 } 281 @Override /* SerializerWriter */ 282 public JsonWriter append(Object text) { 283 super.append(text); 284 return this; 285 } 286 @Override /* SerializerWriter */ 287 public JsonWriter append(String text) { 288 super.append(text); 289 return this; 290 } 291 @Override /* SerializerWriter */ 292 public JsonWriter appendIf(boolean b, String text) { 293 super.appendIf(b, text); 294 return this; 295 } 296 @Override /* SerializerWriter */ 297 public JsonWriter appendIf(boolean b, char c) { 298 super.appendIf(b, c); 299 return this; 300 } 301 @Override /* SerializerWriter */ 302 public JsonWriter append(char c) { 303 super.append(c); 304 return this; 305 } 306}