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