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.marshall.json5l; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import org.apache.juneau.commons.collections.*; 022import org.apache.juneau.marshall.json.*; 023import org.apache.juneau.marshall.jsonl.*; 024 025/** 026 * Serializes POJO models to JSON5L (JSON5 Lines). 027 * 028 * <h5 class='topic'>Media types</h5> 029 * <p> 030 * Handles <c>Accept</c> types: <bc>application/json5l, application/json5lines, text/json5l</bc> 031 * <p> 032 * Produces <c>Content-Type</c> types: <bc>application/json5l</bc> 033 * 034 * <h5 class='topic'>Description</h5> 035 * <p> 036 * JSON5L combines the relaxed JSON5 dialect with JSONL's newline-delimited framing: each top-level 037 * value is written as a compact document on its own line, exactly as {@link JsonlSerializer} does. 038 * <p> 039 * By <b>default</b> the per-line output is strict RFC-8259 JSON (byte-identical to 040 * {@link JsonlSerializer}), keeping output maximally portable. The {@link Builder#json5Sugar() 041 * json5Sugar()} opt-in switches the per-line output to JSON5 sugar (single-quoted strings, unquoted 042 * field names where safe). 043 * 044 * <h5 class='section'>Example:</h5> 045 * <p class='bjava'> 046 * <jc>// Strict-per-line output (default)</jc> 047 * String <jv>json5l</jv> = Json5lSerializer.<jsf>DEFAULT</jsf>.write(<jv>myList</jv>); 048 * 049 * <jc>// JSON5-sugar-per-line output</jc> 050 * Json5lSerializer <jv>serializer</jv> = Json5lSerializer.<jsm>create</jsm>().json5Sugar().build(); 051 * String <jv>json5l</jv> = <jv>serializer</jv>.write(<jv>myList</jv>); 052 * </p> 053 * 054 * <h5 class='figure'>Example output, sugar off (List of beans):</h5> 055 * <p class='bjson'> 056 * {"name":"Alice","age":30} 057 * {"name":"Bob","age":25} 058 * </p> 059 * 060 * <h5 class='figure'>Example output, sugar on:</h5> 061 * <p class='bjson'> 062 * {name:'Alice',age:30} 063 * {name:'Bob',age:25} 064 * </p> 065 * 066 * <h5 class='section'>Notes:</h5><ul> 067 * <li class='note'>This class is thread safe and reusable. 068 * </ul> 069 * 070 * <h5 class='section'>See Also:</h5><ul> 071 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Json5l">JSON5L Basics</a> 072 * </ul> 073 */ 074@SuppressWarnings({ 075 "java:S110", // Inheritance depth acceptable 076 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 077 "java:S9149" // Per-format static factories intentionally shadow the parent's. 078}) 079public class Json5lSerializer extends JsonlSerializer { 080 081 /** 082 * Builder class. 083 */ 084 public static class Builder extends JsonSerializer.Builder<Builder> { 085 086 private static final Cache<HashKey,Json5lSerializer> CACHE = Cache.of(HashKey.class, Json5lSerializer.class).build(); 087 088 boolean json5Sugar; 089 090 /** 091 * Constructor, default settings. 092 */ 093 protected Builder() { 094 produces("application/json5l") 095 .accept("application/json5l,application/json5lines,text/json5l,application/jsonl;q=0.9,application/jsonlines;q=0.9,application/x-ndjson;q=0.9,text/jsonl;q=0.9") 096 .type(Json5lSerializer.class) 097 .useWhitespace(false); 098 } 099 100 /** 101 * Copy constructor. 102 * 103 * @param copyFrom The builder to copy from. 104 * <br>Cannot be <jk>null</jk>. 105 */ 106 protected Builder(Builder copyFrom) { 107 super(reqnn("copyFrom", copyFrom)); 108 json5Sugar = copyFrom.json5Sugar; 109 } 110 111 /** 112 * Copy constructor. 113 * 114 * @param copyFrom The bean to copy from. 115 * <br>Cannot be <jk>null</jk>. 116 */ 117 protected Builder(Json5lSerializer copyFrom) { 118 super(reqnn("copyFrom", copyFrom)); 119 json5Sugar = copyFrom.json5Sugar; 120 } 121 122 /** 123 * Emit JSON5 sugar (single-quoted strings, unquoted field names where safe) on each line 124 * instead of strict RFC-8259 JSON. 125 * 126 * <p> 127 * Off by default, in which case the per-line output is byte-identical to 128 * {@link JsonlSerializer}. 129 * 130 * @return This object. 131 */ 132 public Builder json5Sugar() { 133 json5Sugar = true; 134 return this; 135 } 136 137 @Override /* Overridden from Context.Builder<?> */ 138 public HashKey hashKey() { 139 return HashKey.of(super.hashKey(), json5Sugar); 140 } 141 142 @Override /* Overridden from Context.Builder<?> */ 143 public Json5lSerializer build() { 144 return cache(CACHE).build(Json5lSerializer.class); 145 } 146 147 @Override /* Overridden from Context.Builder<?> */ 148 public Builder copy() { 149 return new Builder(this); 150 } 151 } 152 153 /** Default serializer, strict-JSON-per-line. */ 154 public static final Json5lSerializer DEFAULT = new Json5lSerializer(create()); 155 156 final boolean json5Sugar; 157 158 /** 159 * Creates a new builder for this object. 160 * 161 * @return A new builder. 162 */ 163 public static Builder create() { 164 return new Builder(); 165 } 166 167 /** 168 * Constructor. 169 * 170 * @param builder The builder for this object. 171 */ 172 public Json5lSerializer(Builder builder) { 173 super(builder.useWhitespace(false)); 174 json5Sugar = builder.json5Sugar; 175 } 176 177 /** 178 * Returns <jk>true</jk> if this serializer emits JSON5 sugar on each line. 179 * 180 * @return <jk>true</jk> if JSON5 sugar is enabled. 181 */ 182 public boolean isJson5Sugar() { 183 return json5Sugar; 184 } 185 186 @Override /* Overridden from Context */ 187 public Builder copy() { 188 return new Builder(this); 189 } 190 191 @Override /* Overridden from Context */ 192 public Json5lSerializerSession.Builder createSession() { 193 return Json5lSerializerSession.create(this); 194 } 195 196 @Override /* Overridden from Context */ 197 public Json5lSerializerSession getSession() { 198 return createSession().build(); 199 } 200}