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.AssertionUtils.*; 020import static org.apache.juneau.commons.utils.IoUtils.*; 021import static org.apache.juneau.commons.utils.Utils.*; 022 023import java.io.*; 024import java.lang.reflect.*; 025import java.nio.charset.*; 026import java.util.*; 027import java.util.function.*; 028 029import org.apache.juneau.*; 030import org.apache.juneau.commons.lang.*; 031import org.apache.juneau.httppart.*; 032import org.apache.juneau.serializer.*; 033import org.apache.juneau.svl.*; 034 035/** 036 * Session object that lives for the duration of a single use of {@link JsonSerializer}. 037 * 038 * <h5 class='section'>Notes:</h5><ul> 039 * <li class='warn'>This class is not thread safe and is typically discarded after one use. 040 * </ul> 041 * 042 * <h5 class='section'>See Also:</h5><ul> 043 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JsonBasics">JSON Basics</a> 044 * </ul> 045 */ 046@SuppressWarnings("resource") 047public class JsonSerializerSession extends WriterSerializerSession { 048 /** 049 * Builder class. 050 */ 051 public static class Builder extends WriterSerializerSession.Builder { 052 053 private JsonSerializer ctx; 054 055 /** 056 * Constructor 057 * 058 * @param ctx The context creating this session. 059 * <br>Cannot be <jk>null</jk>. 060 */ 061 protected Builder(JsonSerializer ctx) { 062 super(assertArgNotNull("ctx", ctx)); 063 this.ctx = ctx; 064 } 065 066 @Override /* Overridden from Builder */ 067 public <T> Builder apply(Class<T> type, Consumer<T> apply) { 068 super.apply(type, apply); 069 return this; 070 } 071 072 @Override 073 public JsonSerializerSession build() { 074 return new JsonSerializerSession(this); 075 } 076 077 @Override /* Overridden from Builder */ 078 public Builder debug(Boolean value) { 079 super.debug(value); 080 return this; 081 } 082 083 @Override /* Overridden from Builder */ 084 public Builder fileCharset(Charset value) { 085 super.fileCharset(value); 086 return this; 087 } 088 089 @Override /* Overridden from Builder */ 090 public Builder javaMethod(Method value) { 091 super.javaMethod(value); 092 return this; 093 } 094 095 @Override /* Overridden from Builder */ 096 public Builder locale(Locale value) { 097 super.locale(value); 098 return this; 099 } 100 101 @Override /* Overridden from Builder */ 102 public Builder mediaType(MediaType value) { 103 super.mediaType(value); 104 return this; 105 } 106 107 @Override /* Overridden from Builder */ 108 public Builder mediaTypeDefault(MediaType value) { 109 super.mediaTypeDefault(value); 110 return this; 111 } 112 113 @Override /* Overridden from Builder */ 114 public Builder properties(Map<String,Object> value) { 115 super.properties(value); 116 return this; 117 } 118 119 @Override /* Overridden from Builder */ 120 public Builder property(String key, Object value) { 121 super.property(key, value); 122 return this; 123 } 124 125 @Override /* Overridden from Builder */ 126 public Builder resolver(VarResolverSession value) { 127 super.resolver(value); 128 return this; 129 } 130 131 @Override /* Overridden from Builder */ 132 public Builder schema(HttpPartSchema value) { 133 super.schema(value); 134 return this; 135 } 136 137 @Override /* Overridden from Builder */ 138 public Builder schemaDefault(HttpPartSchema value) { 139 super.schemaDefault(value); 140 return this; 141 } 142 143 @Override /* Overridden from Builder */ 144 public Builder streamCharset(Charset value) { 145 super.streamCharset(value); 146 return this; 147 } 148 149 @Override /* Overridden from Builder */ 150 public Builder timeZone(TimeZone value) { 151 super.timeZone(value); 152 return this; 153 } 154 155 @Override /* Overridden from Builder */ 156 public Builder timeZoneDefault(TimeZone value) { 157 super.timeZoneDefault(value); 158 return this; 159 } 160 161 @Override /* Overridden from Builder */ 162 public Builder unmodifiable() { 163 super.unmodifiable(); 164 return this; 165 } 166 167 @Override /* Overridden from Builder */ 168 public Builder uriContext(UriContext value) { 169 super.uriContext(value); 170 return this; 171 } 172 173 @Override /* Overridden from Builder */ 174 public Builder useWhitespace(Boolean value) { 175 super.useWhitespace(value); 176 return this; 177 } 178 } 179 180 /** 181 * Creates a new builder for this object. 182 * 183 * @param ctx The context creating this session. 184 * <br>Cannot be <jk>null</jk>. 185 * @return A new builder. 186 */ 187 public static Builder create(JsonSerializer ctx) { 188 return new Builder(assertArgNotNull("ctx", ctx)); 189 } 190 191 private final JsonSerializer ctx; 192 193 /** 194 * Constructor. 195 * 196 * @param builder The builder for this object. 197 */ 198 protected JsonSerializerSession(Builder builder) { 199 super(builder); 200 this.ctx = builder.ctx; 201 } 202 203 private SerializerWriter serializeBeanMap(JsonWriter out, BeanMap<?> m, String typeName) throws SerializeException { 204 int i = indent; 205 out.w('{'); 206 207 var addComma = Flag.create(); 208 Predicate<Object> checkNull = x -> isKeepNullProperties() || nn(x); 209 210 if (nn(typeName)) { 211 BeanPropertyMeta pm = m.getMeta().getTypeProperty(); 212 out.cr(i).attr(pm.getName()).w(':').s(i).stringValue(typeName); 213 addComma.set(); 214 } 215 216 m.forEachValue(checkNull, (pMeta, key, value, thrown) -> { 217 var cMeta = pMeta.getClassMeta(); 218 if (nn(thrown)) 219 onBeanGetterException(pMeta, thrown); 220 221 if (canIgnoreValue(cMeta, key, value)) 222 return; 223 224 addComma.ifSet(() -> out.append(',').smi(i)).set(); 225 226 out.cr(i).attr(key).w(':').s(i); 227 228 serializeAnything(out, value, cMeta, key, pMeta); 229 }); 230 231 out.cre(i - 1).w('}'); 232 return out; 233 } 234 235 @SuppressWarnings({ "rawtypes", "unchecked" }) 236 private SerializerWriter serializeCollection(JsonWriter out, Collection c, ClassMeta<?> type) throws SerializeException { 237 238 var elementType = type.getElementType(); 239 240 out.w('['); 241 var addComma = Flag.create(); 242 forEachEntry(c, x -> { 243 addComma.ifSet(() -> out.w(',').smi(indent)).set(); 244 out.cr(indent); 245 serializeAnything(out, x, elementType, "<iterator>", null); 246 }); 247 248 out.cre(indent - 1).w(']'); 249 return out; 250 } 251 252 @SuppressWarnings({ "rawtypes", "unchecked" }) 253 private SerializerWriter serializeMap(JsonWriter out, Map m, ClassMeta<?> type) throws SerializeException { 254 255 var keyType = type.getKeyType(); 256 var valueType = type.getValueType(); 257 258 int i = indent; 259 out.w('{'); 260 261 var addComma = Flag.create(); 262 forEachEntry(m, x -> { 263 addComma.ifSet(() -> out.w(',').smi(i)).set(); 264 Object value = x.getValue(); 265 Object key = generalize(x.getKey(), keyType); 266 out.cr(i).attr(toString(key)).w(':').s(i); 267 serializeAnything(out, value, valueType, (key == null ? null : toString(key)), null); 268 }); 269 270 out.cre(i - 1).w('}'); 271 272 return out; 273 } 274 275 @Override /* Overridden from SerializerSesssion */ 276 protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException { 277 serializeAnything(getJsonWriter(out).i(getInitialDepth()), o, getExpectedRootType(o), "root", null); 278 } 279 280 /** 281 * Returns the language-specific metadata on the specified class. 282 * 283 * @param cm The class to return the metadata on. 284 * @return The metadata. 285 */ 286 protected JsonClassMeta getJsonClassMeta(ClassMeta<?> cm) { 287 return ctx.getJsonClassMeta(cm); 288 } 289 290 /** 291 * Converts the specified output target object to an {@link JsonWriter}. 292 * 293 * @param out The output target object. 294 * @return The output target object wrapped in an {@link JsonWriter}. 295 * @throws IOException Thrown by underlying stream. 296 */ 297 protected final JsonWriter getJsonWriter(SerializerPipe out) throws IOException { 298 var output = out.getRawOutput(); 299 if (output instanceof JsonWriter output2) 300 return output2; 301 var w = new JsonWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isEscapeSolidus(), getQuoteChar(), isSimpleAttrs(), isTrimStrings(), getUriResolver()); 302 out.setWriter(w); 303 return w; 304 } 305 306 /** 307 * Add <js>"_type"</js> properties when needed. 308 * 309 * @see JsonSerializer.Builder#addBeanTypesJson() 310 * @return 311 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 312 * through reflection. 313 */ 314 @Override 315 protected final boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); } 316 317 /** 318 * Prefix solidus <js>'/'</js> characters with escapes. 319 * 320 * @see JsonSerializer.Builder#escapeSolidus() 321 * @return 322 * <jk>true</jk> if solidus (e.g. slash) characters should be escaped. 323 */ 324 protected final boolean isEscapeSolidus() { return ctx.isEscapeSolidus(); } 325 326 /** 327 * Simple JSON attributes. 328 * 329 * @see JsonSerializer.Builder#simpleAttrs() 330 * @return 331 * <jk>true</jk> if JSON attribute names will only be quoted when necessary. 332 * <br>Otherwise, they are always quoted. 333 */ 334 protected final boolean isSimpleAttrs() { return ctx.isSimpleAttrs(); } 335 336 /** 337 * Workhorse method. 338 * Determines the type of object, and then calls the appropriate type-specific serialization method. 339 * 340 * @param out The output writer. 341 * @param o The object to serialize. 342 * @param eType The expected type. 343 * @param attrName The attribute name. 344 * @param pMeta The bean property currently being parsed. 345 * @return The same writer passed in. 346 * @throws SerializeException General serialization error occurred. 347 */ 348 @SuppressWarnings({ "rawtypes" }) 349 protected JsonWriter serializeAnything(JsonWriter out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws SerializeException { 350 351 if (o == null) { 352 out.append("null"); 353 return out; 354 } 355 356 if (eType == null) 357 eType = object(); 358 359 var aType = (ClassMeta<?>)null; // The actual type 360 var sType = (ClassMeta<?>)null; // The serialized type 361 362 aType = push2(attrName, o, eType); 363 boolean isRecursion = aType == null; 364 365 // Handle recursion 366 if (aType == null) { 367 o = null; 368 aType = object(); 369 } 370 371 // Handle Optional<X> 372 if (isOptional(aType)) { 373 o = getOptionalValue(o); 374 eType = getOptionalType(eType); 375 aType = getClassMetaForObject(o, object()); 376 } 377 378 sType = aType; 379 String typeName = getBeanTypeName(this, eType, aType, pMeta); 380 381 // Swap if necessary 382 var swap = aType.getSwap(this); 383 if (nn(swap)) { 384 o = swap(swap, o); 385 sType = swap.getSwapClassMeta(this); 386 387 // If the getSwapClass() method returns Object, we need to figure out 388 // the actual type now. 389 if (sType.isObject()) 390 sType = getClassMetaForObject(o); 391 } 392 393 String wrapperAttr = getJsonClassMeta(sType).getWrapperAttr(); 394 if (nn(wrapperAttr)) { 395 out.w('{').cr(indent).attr(wrapperAttr).w(':').s(indent); 396 indent++; 397 } 398 399 // '\0' characters are considered null. 400 if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) { 401 out.append("null"); 402 } else if (sType.isNumber() || sType.isBoolean()) { 403 out.append(o); 404 } else if (sType.isBean()) { 405 serializeBeanMap(out, toBeanMap(o), typeName); 406 } else if (sType.isUri() || (nn(pMeta) && pMeta.isUri())) { 407 out.uriValue(o); 408 } else if (sType.isMap()) { 409 if (o instanceof BeanMap) 410 serializeBeanMap(out, (BeanMap)o, typeName); 411 else 412 serializeMap(out, (Map)o, eType); 413 } else if (sType.isCollection()) { 414 serializeCollection(out, (Collection)o, eType); 415 } else if (sType.isArray()) { 416 serializeCollection(out, toList(sType.inner(), o), eType); 417 } else if (sType.isReader()) { 418 pipe((Reader)o, out, SerializerSession::handleThrown); 419 } else if (sType.isInputStream()) { 420 pipe((InputStream)o, out, SerializerSession::handleThrown); 421 } else { 422 out.stringValue(toString(o)); 423 } 424 425 if (nn(wrapperAttr)) { 426 indent--; 427 out.cre(indent - 1).w('}'); 428 } 429 430 if (! isRecursion) 431 pop(); 432 return out; 433 } 434 435 /** 436 * Method that can be called from subclasses to serialize an object to JSON. 437 * 438 * <p> 439 * Used by {@link JsonSchemaSerializerSession} for serializing examples to JSON. 440 * 441 * @param o The object to serialize. 442 * @return The serialized object. 443 * @throws Exception Error occurred. 444 */ 445 protected String serializeJson(Object o) throws Exception { 446 var sw = new StringWriter(); 447 serializeAnything(getJsonWriter(createPipe(sw)).i(getInitialDepth()), o, getExpectedRootType(o), "root", null); 448 return sw.toString(); 449 } 450}