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