001/* 002 * Copyright (c) 2007-2022 The Cascading Authors. All Rights Reserved. 003 * 004 * Project and contact information: https://cascading.wensel.net/ 005 * 006 * This file is part of the Cascading project. 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020 021package cascading.nested.json; 022 023import java.io.IOException; 024import java.lang.reflect.Type; 025import java.time.temporal.Temporal; 026import java.time.temporal.TemporalAmount; 027import java.util.Collection; 028import java.util.List; 029import java.util.Map; 030import java.util.function.Function; 031 032import cascading.CascadingException; 033import cascading.nested.core.NestedCoercibleType; 034import cascading.tuple.coerce.Coercions; 035import cascading.tuple.type.CoercibleType; 036import cascading.tuple.type.CoercionFrom; 037import cascading.tuple.type.SerializableType; 038import cascading.tuple.type.ToCanonical; 039import cascading.util.Util; 040import com.fasterxml.jackson.core.JsonParseException; 041import com.fasterxml.jackson.core.JsonProcessingException; 042import com.fasterxml.jackson.databind.DeserializationFeature; 043import com.fasterxml.jackson.databind.JsonNode; 044import com.fasterxml.jackson.databind.Module; 045import com.fasterxml.jackson.databind.ObjectMapper; 046import com.fasterxml.jackson.databind.node.ArrayNode; 047import com.fasterxml.jackson.databind.node.JsonNodeFactory; 048import com.fasterxml.jackson.databind.node.JsonNodeType; 049import heretical.pointer.path.NestedPointerCompiler; 050import heretical.pointer.path.json.JSONNestedPointerCompiler; 051 052/** 053 * Class JSONCoercibleType is a {@link NestedCoercibleType} that provides support for JSON object types. 054 * <p> 055 * Supported values will be maintained as a {@link JsonNode} canonical type within the {@link cascading.tuple.Tuple}. 056 * <p> 057 * Note that {@link #canonical(Object)} will always attempt to parse a String value to a new JsonNode. 058 * If the parse fails, it will return a {@link com.fasterxml.jackson.databind.node.TextNode} instance wrapping the 059 * String value. 060 * <p> 061 * Note the default instance (@link {@link #TYPE}), sets the {@link DeserializationFeature#FAIL_ON_READING_DUP_TREE_KEY} 062 * Jackson property. 063 * <p> 064 * See the {@link #node(Object)}. 065 */ 066public class JSONCoercibleType implements NestedCoercibleType<JsonNode, ArrayNode>, SerializableType 067 { 068 public static final JSONCoercibleType TYPE = new JSONCoercibleType(); 069 070 private ObjectMapper mapper = new ObjectMapper(); 071 072 private JSONCoercibleType() 073 { 074 // prevents json object from being created with duplicate names at the same level 075 mapper.setConfig( mapper.getDeserializationConfig() 076 .with( DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY ) ); 077 } 078 079 /** 080 * Instantiates a new Json coercible type with the given {@link ObjectMapper} instance. 081 * <p> 082 * Use this constructor in order to leverage {@link Module} instances. 083 * <p> 084 * Note the default instance (@link {@link #TYPE}), sets the {@link DeserializationFeature#FAIL_ON_READING_DUP_TREE_KEY} 085 * Jackson property. 086 * 087 * @param mapper the mapper 088 */ 089 public JSONCoercibleType( ObjectMapper mapper ) 090 { 091 this.mapper = mapper; 092 } 093 094 @Override 095 public Class<JsonNode> getCanonicalType() 096 { 097 return JsonNode.class; 098 } 099 100 @Override 101 public <T> ToCanonical<T, JsonNode> from( Type from ) 102 { 103 if( from.getClass() == JSONCoercibleType.class ) 104 return ( v ) -> (JsonNode) v; 105 106 if( from instanceof Class && JsonNode.class.isAssignableFrom( (Class<?>) from ) ) 107 return ( v ) -> (JsonNode) v; 108 109 if( from == String.class ) 110 return ( v ) -> v == null ? null : nodeOrParse( (String) v ); 111 112 if( from == Short.class || from == Short.TYPE ) 113 return ( v ) -> v == null ? null : JsonNodeFactory.instance.numberNode( (Short) v ); 114 115 if( from == Integer.class || from == Integer.TYPE ) 116 return ( v ) -> v == null ? null : JsonNodeFactory.instance.numberNode( (Integer) v ); 117 118 if( from == Long.class || from == Long.TYPE ) 119 return ( v ) -> v == null ? null : JsonNodeFactory.instance.numberNode( (Long) v ); 120 121 if( from == Float.class || from == Float.TYPE ) 122 return ( v ) -> v == null ? null : JsonNodeFactory.instance.numberNode( (Float) v ); 123 124 if( from == Double.class || from == Double.TYPE ) 125 return ( v ) -> v == null ? null : JsonNodeFactory.instance.numberNode( (Double) v ); 126 127 if( from == Boolean.class || from == Boolean.TYPE ) 128 return ( v ) -> v == null ? null : JsonNodeFactory.instance.booleanNode( (Boolean) v ); 129 130 if( from instanceof Class && ( Collection.class.isAssignableFrom( (Class<?>) from ) || Map.class.isAssignableFrom( (Class<?>) from ) ) ) 131 return ( v ) -> v == null ? null : mapper.valueToTree( v ); 132 133 return ( v ) -> v == null ? null : JsonNodeFactory.instance.pojoNode( v ); 134 } 135 136 @Override 137 public JsonNode canonical( Object value ) 138 { 139 if( value == null ) 140 return null; 141 142 Class from = value.getClass(); 143 144 if( JsonNode.class.isAssignableFrom( from ) ) 145 return (JsonNode) value; 146 147 if( from == String.class ) 148 return nodeOrParse( (String) value ); 149 150 if( from == Short.class || from == Short.TYPE ) 151 return JsonNodeFactory.instance.numberNode( (Short) value ); 152 153 if( from == Integer.class || from == Integer.TYPE ) 154 return JsonNodeFactory.instance.numberNode( (Integer) value ); 155 156 if( from == Long.class || from == Long.TYPE ) 157 return JsonNodeFactory.instance.numberNode( (Long) value ); 158 159 if( from == Float.class || from == Float.TYPE ) 160 return JsonNodeFactory.instance.numberNode( (Float) value ); 161 162 if( from == Double.class || from == Double.TYPE ) 163 return JsonNodeFactory.instance.numberNode( (Double) value ); 164 165 if( from == Boolean.class || from == Boolean.TYPE ) 166 return JsonNodeFactory.instance.booleanNode( (Boolean) value ); 167 168 if( Collection.class.isAssignableFrom( from ) || Map.class.isAssignableFrom( from ) ) 169 return mapper.valueToTree( value ); 170 171 return JsonNodeFactory.instance.pojoNode( value ); 172 } 173 174 protected <T> T ifNull( JsonNode node, Function<JsonNode, T> function ) 175 { 176 if( node == null || node.getNodeType() == JsonNodeType.NULL || node.getNodeType() == JsonNodeType.MISSING ) 177 return null; 178 179 return function.apply( node ); 180 } 181 182 @Override 183 public <T> CoercionFrom<JsonNode, T> to( Type to ) 184 { 185 if( to == null || to.getClass() == JSONCoercibleType.class ) 186 return t -> (T) t; 187 188 Class<?> actualTo; 189 190 if( to instanceof CoercibleType ) 191 actualTo = ( (CoercibleType<?>) to ).getCanonicalType(); 192 else 193 actualTo = (Class<?>) to; 194 195 if( JsonNode.class.isAssignableFrom( actualTo ) ) 196 return t -> (T) t; 197 198 if( actualTo == String.class ) 199 return node -> ifNull( node, n -> n.getNodeType() == JsonNodeType.STRING ? (T) n.textValue() : (T) textOrWrite( n ) ); 200 201 if( actualTo == Short.class || actualTo == Short.TYPE ) 202 return node -> ifNull( node, n -> n.getNodeType() == JsonNodeType.NUMBER ? (T) Short.valueOf( n.shortValue() ) : Coercions.coerce( textOrWrite( n ), to ) ); 203 204 if( actualTo == Integer.class || actualTo == Integer.TYPE ) 205 return node -> ifNull( node, n -> n.getNodeType() == JsonNodeType.NUMBER ? (T) Integer.valueOf( n.intValue() ) : Coercions.coerce( textOrWrite( n ), to ) ); 206 207 if( actualTo == Long.class || actualTo == Long.TYPE ) 208 return node -> ifNull( node, n -> n.getNodeType() == JsonNodeType.NUMBER ? (T) Long.valueOf( n.longValue() ) : Coercions.coerce( textOrWrite( n ), to ) ); 209 210 if( actualTo == Float.class || actualTo == Float.TYPE ) 211 return node -> ifNull( node, n -> n.getNodeType() == JsonNodeType.NUMBER ? (T) Float.valueOf( n.floatValue() ) : Coercions.coerce( textOrWrite( n ), to ) ); 212 213 if( actualTo == Double.class || actualTo == Double.TYPE ) 214 return node -> ifNull( node, n -> n.getNodeType() == JsonNodeType.NUMBER ? (T) Double.valueOf( n.doubleValue() ) : Coercions.coerce( textOrWrite( n ), to ) ); 215 216 if( actualTo == Boolean.class || actualTo == Boolean.TYPE ) 217 return node -> ifNull( node, n -> n.getNodeType() == JsonNodeType.BOOLEAN ? (T) Boolean.valueOf( n.booleanValue() ) : Coercions.coerce( textOrWrite( n ), to ) ); 218 219 if( Map.class.isAssignableFrom( actualTo ) ) 220 return node -> ifNull( node, n -> (T) convertTree( n, actualTo ) ); 221 222 if( List.class.isAssignableFrom( actualTo ) ) 223 return node -> ifNull( node, n -> (T) convertTree( n, actualTo ) ); 224 225 if( Temporal.class.isAssignableFrom( actualTo ) ) 226 return node -> ifNull( node, n -> (T) convertTree( n, actualTo ) ); 227 228 if( TemporalAmount.class.isAssignableFrom( actualTo ) ) 229 return node -> ifNull( node, n -> (T) convertTree( n, actualTo ) ); 230 231 if( to instanceof CoercibleType ) 232 return node -> ifNull( node, n -> (T) ( (CoercibleType<?>) to ).canonical( textOrWrite( n ) ) ); 233 234 throw new CascadingException( "unknown type coercion requested, from: " + Util.getTypeName( JsonNode.class ) + " to: " + Util.getTypeName( to ) ); 235 } 236 237 @Override 238 public <Coerce> Coerce coerce( Object value, Type to ) 239 { 240 if( to == null || to.getClass() == JSONCoercibleType.class ) 241 return (Coerce) value; 242 243 if( value == null ) 244 return null; 245 246 Class<?> from = value.getClass(); 247 248 if( !JsonNode.class.isAssignableFrom( from ) ) 249 throw new IllegalStateException( "was not normalized, got: " + from.getName() ); 250 251 JsonNode node = (JsonNode) value; 252 253 if( node.isMissingNode() ) 254 return null; 255 256 JsonNodeType nodeType = node.getNodeType(); 257 258 if( nodeType == JsonNodeType.NULL ) 259 return null; 260 261 Class<?> actualTo; 262 263 if( to instanceof CoercibleType ) 264 actualTo = ( (CoercibleType<?>) to ).getCanonicalType(); 265 else 266 actualTo = (Class<?>) to; 267 268 // support sub-classes of JsonNode 269 if( JsonNode.class.isAssignableFrom( actualTo ) ) 270 return (Coerce) node; 271 272 if( actualTo == String.class ) 273 return nodeType == JsonNodeType.STRING ? (Coerce) node.textValue() : (Coerce) textOrWrite( node ); 274 275 if( actualTo == Short.class || actualTo == Short.TYPE ) 276 return nodeType == JsonNodeType.NUMBER ? (Coerce) Short.valueOf( node.shortValue() ) : (Coerce) Coercions.coerce( textOrWrite( node ), to ); 277 278 if( actualTo == Integer.class || actualTo == Integer.TYPE ) 279 return nodeType == JsonNodeType.NUMBER ? (Coerce) Integer.valueOf( node.intValue() ) : (Coerce) Coercions.coerce( textOrWrite( node ), to ); 280 281 if( actualTo == Long.class || actualTo == Long.TYPE ) 282 return nodeType == JsonNodeType.NUMBER ? (Coerce) Long.valueOf( node.longValue() ) : (Coerce) Coercions.coerce( textOrWrite( node ), to ); 283 284 if( actualTo == Float.class || actualTo == Float.TYPE ) 285 return nodeType == JsonNodeType.NUMBER ? (Coerce) Float.valueOf( node.floatValue() ) : (Coerce) Coercions.coerce( textOrWrite( node ), to ); 286 287 if( actualTo == Double.class || actualTo == Double.TYPE ) 288 return nodeType == JsonNodeType.NUMBER ? (Coerce) Double.valueOf( node.doubleValue() ) : (Coerce) Coercions.coerce( textOrWrite( node ), to ); 289 290 if( actualTo == Boolean.class || actualTo == Boolean.TYPE ) 291 return nodeType == JsonNodeType.BOOLEAN ? (Coerce) Boolean.valueOf( node.booleanValue() ) : (Coerce) Coercions.coerce( textOrWrite( node ), to ); 292 293 if( Map.class.isAssignableFrom( actualTo ) ) 294 return (Coerce) convertTree( node, actualTo ); 295 296 if( List.class.isAssignableFrom( actualTo ) ) 297 return (Coerce) convertTree( node, actualTo ); 298 299 if( Temporal.class.isAssignableFrom( actualTo ) ) 300 return (Coerce) convertTree( node, actualTo ); 301 302 if( TemporalAmount.class.isAssignableFrom( actualTo ) ) 303 return (Coerce) convertTree( node, actualTo ); 304 305 if( to instanceof CoercibleType ) 306 return (Coerce) ( (CoercibleType<?>) to ).canonical( textOrWrite( node ) ); 307 308 throw new CascadingException( "unknown type coercion requested, from: " + Util.getTypeName( from ) + " to: " + Util.getTypeName( to ) ); 309 } 310 311 private Object convertTree( JsonNode value, Class<?> to ) 312 { 313 try 314 { 315 return mapper.treeToValue( value, to ); 316 } 317 catch( JsonProcessingException exception ) 318 { 319 throw new CascadingException( "unable to coerce json node into " + to.getName(), exception ); 320 } 321 } 322 323 private String textOrWrite( JsonNode value ) 324 { 325 if( value != null && value.isTextual() ) 326 return value.textValue(); 327 328 try 329 { 330 return write( value ); 331 } 332 catch( JsonProcessingException exception ) 333 { 334 throw new CascadingException( "unable to write value as json", exception ); 335 } 336 } 337 338 private String write( JsonNode value ) throws JsonProcessingException 339 { 340 return mapper.writeValueAsString( value ); 341 } 342 343 private JsonNode nodeOrParse( String value ) 344 { 345 try 346 { 347 return parse( value ); // presume this is a JSON string 348 } 349 catch( JsonParseException exception ) 350 { 351 return JsonNodeFactory.instance.textNode( value ); 352 } 353 catch( IOException exception ) 354 { 355 throw new CascadingException( "unable to read json", exception ); 356 } 357 } 358 359 private JsonNode parse( String value ) throws IOException 360 { 361 return mapper.readTree( value ); 362 } 363 364 @Override 365 public NestedPointerCompiler<JsonNode, ArrayNode> getNestedPointerCompiler() 366 { 367 return JSONNestedPointerCompiler.COMPILER; 368 } 369 370 @Override 371 public JsonNode deepCopy( JsonNode jsonNode ) 372 { 373 if( jsonNode == null ) 374 return null; 375 376 return jsonNode.deepCopy(); 377 } 378 379 @Override 380 public JsonNode newRoot() 381 { 382 return JsonNodeFactory.instance.objectNode(); 383 } 384 385 @Override 386 public Class getSerializer( Class base ) 387 { 388 // required to defer classloading 389 if( base == org.apache.hadoop.io.serializer.Serialization.class ) 390 return cascading.nested.json.hadoop2.JSONHadoopSerialization.class; 391 392 return null; 393 } 394 395 @Override 396 public String toString() 397 { 398 return getClass().getName(); 399 } 400 401 @Override 402 public int hashCode() 403 { 404 return getCanonicalType().hashCode(); 405 } 406 407 @Override 408 public boolean equals( Object object ) 409 { 410 if( this == object ) 411 return true; 412 413 if( !( object instanceof CoercibleType ) ) 414 return false; 415 416 return getCanonicalType().equals( ( (CoercibleType) object ).getCanonicalType() ); 417 } 418 }