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