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.jcs; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.io.*; 022 023import org.apache.juneau.marshall.json.*; 024import org.apache.juneau.marshall.marshaller.*; 025import org.apache.juneau.marshall.serializer.*; 026import org.apache.juneau.marshall.stream.*; 027 028/** 029 * Serializes POJO models to canonical JSON per RFC 8785 (JCS). 030 * 031 * <h5 class='topic'>Media types</h5> 032 * <p> 033 * Produces media type: <bc>application/jcs+json</bc> 034 * </p> 035 * 036 * <h5 class='topic'>Description</h5> 037 * <p> 038 * JCS (JSON Canonicalization Scheme) produces a deterministic, byte-for-byte canonical 039 * representation of JSON, enabling reliable cryptographic operations such as hashing and 040 * digital signing. All canonicalization rules are defined in 041 * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc8785">RFC 8785</a>. 042 * </p> 043 * <ul class='spaced-list'> 044 * <li>No whitespace between tokens. 045 * <li>Object properties sorted by UTF-16 code unit order, applied recursively to all nested objects. 046 * <li>Numbers serialized using ECMAScript-compatible rules: shortest round-trip representation, 047 * no trailing zeros, lowercase <code>e</code>, positive exponent sign included (e.g. <code>1e+30</code>). 048 * <li>Negative zero serialized as <code>0</code>. 049 * <li>Non-ASCII characters emitted as literal UTF-8 bytes (no unnecessary unicode escape sequences). 050 * <li>Array element order is preserved (not sorted). 051 * </ul> 052 * 053 * <h5 class='section'>Example:</h5> 054 * <p class='bjava'> 055 * <jc>// Create a bean.</jc> 056 * MyBean <jv>bean</jv> = <jk>new</jk> MyBean().name(<js>"Alice"</js>).age(30); 057 * 058 * <jc>// Serialize to canonical JSON.</jc> 059 * String <jv>json</jv> = JcsSerializer.<jsf>DEFAULT</jsf>.write(<jv>bean</jv>); 060 * 061 * <jc>// Or use the Jcs marshaller convenience method.</jc> 062 * String <jv>json</jv> = Jcs.<jsm>of</jsm>(<jv>bean</jv>); 063 * </p> 064 * 065 * <h5 class='figure'>Example output (bean with name/age):</h5> 066 * <p class='bjson'> 067 * {<jok>"age"</jok>:<jov>30</jov>,<jok>"name"</jok>:<jov>"Alice"</jov>} 068 * </p> 069 * 070 * <h5 class='figure'>Example output with nested object:</h5> 071 * <p class='bjson'> 072 * {<jok>"address"</jok>:{<jok>"city"</jok>:<jov>"Denver"</jov>,<jok>"zip"</jok>:<jov>"80201"</jov>},<jok>"name"</jok>:<jov>"Alice"</jov>} 073 * </p> 074 * 075 * <h5 class='section'>Notes:</h5><ul> 076 * <li class='note'>This class is thread safe and reusable. 077 * <li class='note'> 078 * JCS output is valid JSON and can be parsed using the standard {@link JsonParser}. 079 * The {@link Jcs} marshaller pairs this serializer with {@link JsonParser} for full round-trip support. 080 * <li class='note'> 081 * {@link java.math.BigDecimal} and {@link java.math.BigInteger} values beyond IEEE 754 double 082 * precision range will lose precision or throw a {@link SerializeException}. 083 * This is a spec-level constraint defined by RFC 8785. 084 * <li class='note'> 085 * {@link Double#NaN}, {@link Double#POSITIVE_INFINITY}, and {@link Double#NEGATIVE_INFINITY} 086 * are not permitted and will throw a {@link SerializeException}. 087 * <li class='note'> 088 * Strings containing lone UTF-16 surrogate code units will throw {@link SerializeException}. 089 * <li class='note'> 090 * Uses <code>application/jcs+json</code> to avoid content-negotiation collision with 091 * {@link JsonSerializer} (<code>application/json</code>). Request explicitly via 092 * <c>Accept: application/jcs+json</c> for canonical output. 093 * </ul> 094 * 095 * <h5 class='section'>See Also:</h5><ul> 096 * <li class='link'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc8785">RFC 8785 — JSON Canonicalization Scheme</a> 097 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Jcs">JCS</a> 098 * <li class='jc'>{@link Jcs} 099 * <li class='jc'>{@link JsonParser} 100 * </ul> 101 */ 102@SuppressWarnings({ 103 "java:S110", // Inheritance depth acceptable for this class hierarchy 104 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 105 "java:S9149", // Per-format static factories intentionally shadow the parent's. 106 "resource" // Closeable resources are owned by the caller's serializer session; Eclipse JDT @Owning warning is by design. 107}) 108public class JcsSerializer extends JsonSerializer { 109 110 /** 111 * Builder class. 112 */ 113 public static class Builder extends JsonSerializer.Builder<Builder> { 114 115 /** 116 * Constructor, default settings for JCS canonical output. 117 */ 118 protected Builder() { 119 super(); 120 produces("application/jcs+json"); 121 accept("application/jcs+json"); 122 quoteChar('"'); 123 useWhitespace(false); 124 } 125 126 /** 127 * Copy constructor. 128 * 129 * @param copyFrom The builder to copy from. 130 * <br>Cannot be <jk>null</jk>. 131 */ 132 protected Builder(Builder copyFrom) { 133 super(reqnn("copyFrom", copyFrom)); 134 } 135 136 /** 137 * Copy constructor. 138 * 139 * @param copyFrom The serializer to copy from. 140 * <br>Cannot be <jk>null</jk>. 141 */ 142 protected Builder(JcsSerializer copyFrom) { 143 super(reqnn("copyFrom", copyFrom)); 144 } 145 146 @Override /* Overridden from Context.Builder<?> */ 147 public Builder copy() { 148 return new Builder(this); 149 } 150 151 @Override /* Overridden from Context.Builder<?> */ 152 public JcsSerializer build() { 153 return new JcsSerializer(this); 154 } 155 } 156 157 /** Default serializer. */ 158 public static final JcsSerializer DEFAULT = new JcsSerializer(create()); 159 160 /** 161 * Creates a new builder for this object. 162 * 163 * @return A new builder. 164 */ 165 public static Builder create() { 166 return new Builder(); 167 } 168 169 /** 170 * Constructor. 171 * 172 * @param builder The builder for this object. 173 */ 174 public JcsSerializer(Builder builder) { 175 super(builder); 176 } 177 178 @Override /* Overridden from Context */ 179 public Builder copy() { 180 return new Builder(this); 181 } 182 183 @Override /* Overridden from JsonSerializer */ 184 public JcsSerializerSession.Builder createSession() { 185 return JcsSerializerSession.create(this); 186 } 187 188 /** 189 * Convenience delegator (default session args) for the raw-JSON token writer. Real impl on 190 * {@link JcsSerializerSession#writeTokens(Object)}. 191 * 192 * <p> 193 * <b>Note:</b> the cursor's structural methods ({@link TokenWriter#startObject() startObject}, 194 * {@link TokenWriter#fieldName(String) fieldName}, {@link TokenWriter#string(String) string}, ...) 195 * produce ordinary JSON; they do <em>not</em> apply the JCS canonicalization rules (sorted 196 * keys, normalized number formatting, etc.) that {@link #write(Object)} produces. The 197 * {@link TokenWriter#object(Object) object(Object)} bridge is <b>disabled</b> on this writer. 198 * Use {@link #write(Object)} for canonical output. 199 * 200 * @param output The output. 201 * @return A new {@link JsonTokenWriter} with {@code object(...)} disabled. 202 * @throws IOException If the output type is not supported or could not be opened. 203 */ 204 @Override /* TokenWritable */ 205 public TokenWriter writeTokens(Object output) throws IOException { 206 return getSession().writeTokens(output); 207 } 208 209 /** 210 * Convenience delegator (default session args) for a record writer that emits canonical JCS 211 * for the value passed to {@link RecordWriter#write(Object) write(...)}. Real impl on 212 * {@link JcsSerializerSession#writeRecords(Object)}. 213 * 214 * @param output The output. 215 * @return A new {@link RecordWriter}. 216 * @throws IOException If the output type is not supported or could not be opened. 217 */ 218 @Override /* RecordWritable */ 219 public RecordWriter writeRecords(Object output) throws IOException { 220 return getSession().writeRecords(output); 221 } 222 223 @Override /* RecordWritable */ 224 public boolean isRecordStreaming() { return false; } 225}