|
26 | 26 | import static org.apache.beam.sdk.schemas.Schema.TypeName.INT16; |
27 | 27 | import static org.apache.beam.sdk.schemas.Schema.TypeName.INT32; |
28 | 28 | import static org.apache.beam.sdk.schemas.Schema.TypeName.INT64; |
| 29 | +import static org.apache.beam.sdk.schemas.Schema.TypeName.MAP; |
29 | 30 | import static org.apache.beam.sdk.schemas.Schema.TypeName.STRING; |
30 | 31 | import static org.apache.beam.sdk.util.RowJsonValueExtractors.booleanValueExtractor; |
31 | 32 | import static org.apache.beam.sdk.util.RowJsonValueExtractors.byteValueExtractor; |
|
57 | 58 | import java.time.LocalDate; |
58 | 59 | import java.time.LocalDateTime; |
59 | 60 | import java.time.LocalTime; |
| 61 | +import java.util.HashMap; |
| 62 | +import java.util.Iterator; |
| 63 | +import java.util.Map; |
60 | 64 | import java.util.stream.Stream; |
61 | 65 | import java.util.stream.StreamSupport; |
62 | 66 | import org.apache.beam.sdk.schemas.Schema; |
|
95 | 99 | }) |
96 | 100 | public class RowJson { |
97 | 101 | private static final ImmutableSet<TypeName> SUPPORTED_TYPES = |
98 | | - ImmutableSet.of(BYTE, INT16, INT32, INT64, FLOAT, DOUBLE, BOOLEAN, STRING, DECIMAL, DATETIME); |
| 102 | + ImmutableSet.of( |
| 103 | + BYTE, INT16, INT32, INT64, FLOAT, DOUBLE, BOOLEAN, STRING, DECIMAL, DATETIME, MAP); |
99 | 104 | private static final ImmutableSet<String> KNOWN_LOGICAL_TYPE_IDENTIFIERS = |
100 | 105 | ImmutableSet.of( |
101 | 106 | SqlTypes.DATE.getIdentifier(), |
@@ -160,6 +165,14 @@ private static ImmutableList<UnsupportedField> findUnsupportedFields( |
160 | 165 | return findUnsupportedFields(fieldType.getCollectionElementType(), fieldName + "[]"); |
161 | 166 | } |
162 | 167 |
|
| 168 | + if (fieldTypeName.isMapType()) { |
| 169 | + if (!STRING.equals(fieldType.getMapKeyType().getTypeName())) { |
| 170 | + return ImmutableList.of( |
| 171 | + new UnsupportedField(fieldName + ".key", fieldType.getMapKeyType().getTypeName())); |
| 172 | + } |
| 173 | + return findUnsupportedFields(fieldType.getMapValueType(), fieldName + "{}"); |
| 174 | + } |
| 175 | + |
163 | 176 | if (fieldTypeName.isLogicalType()) { |
164 | 177 | if (KNOWN_LOGICAL_TYPE_IDENTIFIERS.contains(fieldType.getLogicalType().getIdentifier())) { |
165 | 178 | return ImmutableList.of(); |
@@ -303,6 +316,10 @@ private Object extractJsonNodeValue(FieldValue fieldValue) { |
303 | 316 | return jsonArrayToList(fieldValue); |
304 | 317 | } |
305 | 318 |
|
| 319 | + if (fieldValue.isMapType()) { |
| 320 | + return jsonObjectToMap(fieldValue); |
| 321 | + } |
| 322 | + |
306 | 323 | if (fieldValue.typeName().isLogicalType()) { |
307 | 324 | String identifier = fieldValue.type().getLogicalType().getIdentifier(); |
308 | 325 | if (SqlTypes.DATE.getIdentifier().equals(identifier)) { |
@@ -365,6 +382,32 @@ private Object jsonArrayToList(FieldValue arrayFieldValue) { |
365 | 382 | .collect(toImmutableList()); |
366 | 383 | } |
367 | 384 |
|
| 385 | + private Map<String, Object> jsonObjectToMap(FieldValue mapFieldValue) { |
| 386 | + if (!mapFieldValue.isJsonObject()) { |
| 387 | + throw new UnsupportedRowJsonException( |
| 388 | + "Expected JSON object for field '" |
| 389 | + + mapFieldValue.name() |
| 390 | + + "'. Instead got " |
| 391 | + + mapFieldValue.jsonNodeType().name()); |
| 392 | + } |
| 393 | + |
| 394 | + Map<String, Object> result = new HashMap<>(); |
| 395 | + Iterator<Map.Entry<String, JsonNode>> fields = mapFieldValue.jsonValue().fields(); |
| 396 | + while (fields.hasNext()) { |
| 397 | + Map.Entry<String, JsonNode> field = fields.next(); |
| 398 | + String key = field.getKey(); |
| 399 | + JsonNode value = field.getValue(); |
| 400 | + |
| 401 | + Object extractedValue = |
| 402 | + extractJsonNodeValue( |
| 403 | + FieldValue.of( |
| 404 | + mapFieldValue.name() + "['" + key + "']", mapFieldValue.mapValueType(), value)); |
| 405 | + |
| 406 | + result.put(key, extractedValue); |
| 407 | + } |
| 408 | + return result; |
| 409 | + } |
| 410 | + |
368 | 411 | private static Object extractJsonPrimitiveValue(FieldValue fieldValue) { |
369 | 412 | try { |
370 | 413 | return JSON_VALUE_GETTERS.get(fieldValue.typeName()).extractValue(fieldValue.jsonValue()); |
@@ -440,6 +483,18 @@ Schema rowSchema() { |
440 | 483 | return type().getRowSchema(); |
441 | 484 | } |
442 | 485 |
|
| 486 | + boolean isMapType() { |
| 487 | + return TypeName.MAP.equals(type().getTypeName()); |
| 488 | + } |
| 489 | + |
| 490 | + FieldType mapKeyType() { |
| 491 | + return type().getMapKeyType(); |
| 492 | + } |
| 493 | + |
| 494 | + FieldType mapValueType() { |
| 495 | + return type().getMapValueType(); |
| 496 | + } |
| 497 | + |
443 | 498 | static FieldValue of(String name, FieldType type, JsonNode jsonValue) { |
444 | 499 | return new AutoValue_RowJson_RowJsonDeserializer_FieldValue(name, type, jsonValue); |
445 | 500 | } |
@@ -538,6 +593,14 @@ private void writeValue(JsonGenerator gen, FieldType type, Object value) throws |
538 | 593 | case ROW: |
539 | 594 | writeRow((Row) value, type.getRowSchema(), gen); |
540 | 595 | break; |
| 596 | + case MAP: |
| 597 | + gen.writeStartObject(); |
| 598 | + for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) value).entrySet()) { |
| 599 | + gen.writeFieldName(entry.getKey().toString()); |
| 600 | + writeValue(gen, type.getMapValueType(), entry.getValue()); |
| 601 | + } |
| 602 | + gen.writeEndObject(); |
| 603 | + break; |
541 | 604 | case LOGICAL_TYPE: |
542 | 605 | String identifier = type.getLogicalType().getIdentifier(); |
543 | 606 | if (SqlTypes.DATE.getIdentifier().equals(identifier)) { |
|
0 commit comments