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.bean.jsonpatch; 018 019import java.io.*; 020import java.lang.reflect.*; 021 022import org.apache.juneau.marshall.json.*; 023import org.apache.juneau.marshall.marshaller.*; 024import org.apache.juneau.marshall.parser.*; 025import org.apache.juneau.marshall.serializer.*; 026 027/** 028 * A pairing of a {@link JsonSerializer} and {@link JsonParser} into a single class with convenience to/of methods, 029 * pre-wired for JSON Patch's {@link JsonPatchOperation} discriminator. 030 * 031 * <p> 032 * Every {@link JsonSerializer}/{@link JsonParser} used to read or write {@link JsonPatchOperation} (or 033 * {@link JsonPatch}) needs to be configured with 034 * <c>.typePropertyName(JsonPatchOperation.<jk>class</jk>, <js>"op"</js>)</c> so that polymorphic dispatch across 035 * {@link AddOp}, {@link RemoveOp}, {@link ReplaceOp}, {@link MoveOp}, {@link CopyOp}, and {@link TestOp} resolves 036 * via the RFC 6902 {@code "op"} member rather than the default {@code "_type"} property. This class hand-wires 037 * that configuration once so callers don't have to repeat it. 038 * 039 * <h5 class='figure'>Examples:</h5> 040 * <p class='bjava'> 041 * <jc>// Using instance.</jc> 042 * JsonPatchMarshaller <jv>m</jv> = <jk>new</jk> JsonPatchMarshaller(); 043 * JsonPatch <jv>patch</jv> = <jv>m</jv>.read(<jv>string</jv>, JsonPatch.<jk>class</jk>); 044 * String <jv>string</jv> = <jv>m</jv>.write(<jv>patch</jv>); 045 * </p> 046 * <p class='bjava'> 047 * <jc>// Using DEFAULT instance.</jc> 048 * JsonPatch <jv>patch</jv> = JsonPatchMarshaller.<jsf>DEFAULT</jsf>.read(<jv>string</jv>, JsonPatch.<jk>class</jk>); 049 * String <jv>string</jv> = JsonPatchMarshaller.<jsf>DEFAULT</jsf>.write(<jv>patch</jv>); 050 * </p> 051 * <p class='bjava'> 052 * <jc>// Using static shortcuts.</jc> 053 * JsonPatch <jv>patch</jv> = JsonPatchMarshaller.<jsm>to</jsm>(<jv>string</jv>, JsonPatch.<jk>class</jk>); 054 * String <jv>string</jv> = JsonPatchMarshaller.<jsm>of</jsm>(<jv>patch</jv>); 055 * </p> 056 * 057 * <h5 class='figure'>Example output:</h5> 058 * <p class='bjson'> 059 * [{<js>"op"</js>:<js>"add"</js>,<js>"path"</js>:<js>"/a/b/c"</js>,<js>"value"</js>:<js>"foo"</js>},{<js>"op"</js>:<js>"remove"</js>,<js>"path"</js>:<js>"/a/b/c"</js>}] 060 * </p> 061 * 062 * <h5 class='section'>See Also:</h5><ul> 063 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Marshallers">Marshallers</a> 064 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanJsonPatch">juneau-bean-jsonpatch</a> 065 * </ul> 066 */ 067public class JsonPatchMarshaller extends CharMarshaller { 068 069 /** 070 * Default {@link JsonSerializer}, pre-wired with the {@code "op"} discriminator for {@link JsonPatchOperation}. 071 */ 072 public static final JsonSerializer SERIALIZER = JsonSerializer.create() 073 .addBeanTypes() 074 .addRootType() 075 .typePropertyName(JsonPatchOperation.class, "op") 076 .build(); 077 078 /** 079 * Default {@link JsonParser}, pre-wired with the {@code "op"} discriminator for {@link JsonPatchOperation}. 080 */ 081 public static final JsonParser PARSER = JsonParser.create() 082 .typePropertyName(JsonPatchOperation.class, "op") 083 .build(); 084 085 /** Default marshaller instance. */ 086 public static final JsonPatchMarshaller DEFAULT = new JsonPatchMarshaller(); 087 088 /** 089 * Serializes a POJO to a <c>String</c> using the {@link #DEFAULT} marshaller. 090 * 091 * <p> 092 * A shortcut for calling <c><jsf>DEFAULT</jsf>.write(<jv>object</jv>)</c>. 093 * 094 * @param object The object to serialize. 095 * @return The serialized object. 096 * @throws SerializeException If a problem occurred trying to convert the output. 097 */ 098 public static String of(Object object) throws SerializeException { 099 return DEFAULT.write(object); 100 } 101 102 /** 103 * Parses an input into the specified object type using the {@link #DEFAULT} marshaller. 104 * 105 * <p> 106 * A shortcut for calling <c><jsf>DEFAULT</jsf>.read(<jv>input</jv>, <jv>type</jv>)</c>. 107 * 108 * @param <T> The class type of the object being created. 109 * @param input The input. 110 * @param type The object type to create. 111 * @return The parsed object. 112 * @throws ParseException Malformed input encountered. 113 */ 114 public static <T> T to(String input, Class<T> type) throws ParseException { 115 return DEFAULT.read(input, type); 116 } 117 118 /** 119 * Parses an input into the specified parameterized object type using the {@link #DEFAULT} marshaller. 120 * 121 * <p> 122 * A shortcut for calling <c><jsf>DEFAULT</jsf>.read(<jv>input</jv>, <jv>type</jv>, <jv>args</jv>)</c>. 123 * 124 * @param <T> The class type of the object to create. 125 * @param input The input. 126 * @param type The object type to create. 127 * @param args The type arguments of the class if it's a collection or map. 128 * @return The parsed object. 129 * @throws ParseException Malformed input encountered. 130 */ 131 public static <T> T to(String input, Type type, Type... args) throws ParseException { 132 return DEFAULT.read(input, type, args); 133 } 134 135 /** 136 * Parses the contents of a {@link Reader} into the specified object type using the {@link #DEFAULT} marshaller. 137 * 138 * <p> 139 * A shortcut for calling <c><jsf>DEFAULT</jsf>.read(<jv>input</jv>, <jv>type</jv>)</c> that catches any 140 * {@link IOException} from the underlying stream and rethrows it as an unchecked {@link ParseException}, so the 141 * caller is not burdened with a checked exception. 142 * 143 * @param <T> The class type of the object being created. 144 * @param input The input reader. 145 * @param type The object type to create. 146 * @return The parsed object. 147 * @throws ParseException Malformed input encountered or an I/O error occurred on the underlying stream. 148 */ 149 public static <T> T to(Reader input, Class<T> type) throws ParseException { 150 try { 151 return DEFAULT.read(input, type); 152 } catch (IOException e) { 153 throw new ParseException(e); 154 } 155 } 156 157 /** 158 * Parses the contents of a {@link Reader} into the specified parameterized object type using the {@link #DEFAULT} marshaller. 159 * 160 * <p> 161 * A shortcut for calling <c><jsf>DEFAULT</jsf>.read(<jv>input</jv>, <jv>type</jv>, <jv>args</jv>)</c> that catches any 162 * {@link IOException} from the underlying stream and rethrows it as an unchecked {@link ParseException}, so the 163 * caller is not burdened with a checked exception. 164 * 165 * @param <T> The class type of the object to create. 166 * @param input The input reader. 167 * @param type The object type to create. 168 * @param args The type arguments of the class if it's a collection or map. 169 * @return The parsed object. 170 * @throws ParseException Malformed input encountered or an I/O error occurred on the underlying stream. 171 */ 172 public static <T> T to(Reader input, Type type, Type... args) throws ParseException { 173 try { 174 return DEFAULT.read(input, type, args); 175 } catch (IOException e) { 176 throw new ParseException(e); 177 } 178 } 179 180 /** 181 * Serializes a POJO to the specified {@link Writer} using the {@link #DEFAULT} marshaller. 182 * 183 * <p> 184 * A shortcut for calling <c><jsf>DEFAULT</jsf>.write(<jv>object</jv>, <jv>output</jv>)</c> that catches any 185 * {@link IOException} from the underlying stream and rethrows it as an unchecked {@link SerializeException}, so the 186 * caller is not burdened with a checked exception. 187 * 188 * @param object The object to serialize. 189 * @param output The writer to serialize to. 190 * @throws SerializeException If a problem occurred trying to convert the output or an I/O error occurred on the underlying stream. 191 */ 192 public static void of(Object object, Writer output) throws SerializeException { 193 try { 194 DEFAULT.write(object, output); 195 } catch (IOException e) { // HTT: required by the checked IOException on Marshaller.write(); DEFAULT's serializer wraps every writer and pipe I/O failure in SerializeException or a runtime exception first, and a seam would make this testable 196 throw new SerializeException(e); // HTT: only reachable from the catch above 197 } 198 } 199 200 /** Creates using the pre-wired {@link #SERIALIZER} and {@link #PARSER}. */ 201 public JsonPatchMarshaller() { 202 this(SERIALIZER, PARSER); 203 } 204 205 /** 206 * Creates with custom serializer and parser. 207 * 208 * <p> 209 * Callers overriding these should preserve the <c>.typePropertyName(JsonPatchOperation.class, "op")</c> 210 * configuration (and, on the serializer side, <c>.addBeanTypes()</c>/<c>.addRootType()</c>) or polymorphic 211 * dispatch across the {@link JsonPatchOperation} subclasses will not round-trip correctly. 212 * 213 * @param s The serializer. 214 * @param p The parser. 215 */ 216 public JsonPatchMarshaller(JsonSerializer s, JsonParser p) { 217 super(s, p); 218 } 219}