All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.uber.hoodie.avro.MercifulJsonConverter Maven / Gradle / Ivy

There is a newer version: 0.4.7
Show newest version
/*
 * Copyright (c) 2016 Uber Technologies, Inc. ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.uber.hoodie.avro;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericRecord;
import org.codehaus.jackson.map.ObjectMapper;

/**
 * Marjority of this is copied from https://github.com/jwills/avro-json/blob/master/src/main/java/com/cloudera/science/avro/common/JsonConverter.java
 * Adjusted for expected behavior of our use cases
 */
public class MercifulJsonConverter {

  private final ObjectMapper mapper = new ObjectMapper();
  private final Schema baseSchema;

  public MercifulJsonConverter(Schema schema) {
    this.baseSchema = schema;
  }


  public GenericRecord convert(String json) throws IOException {
    try {
      return convert(mapper.readValue(json, Map.class), baseSchema);
    } catch (IOException e) {
      throw new IOException("Failed to parse as Json: " + json + "\n\n" + e.getMessage());
    }
  }

  private GenericRecord convert(Map raw, Schema schema)
      throws IOException {
    GenericRecord result = new GenericData.Record(schema);
    for (Schema.Field f : schema.getFields()) {
      String name = f.name();
      Object rawValue = raw.get(name);
      if (rawValue != null) {
        result.put(f.pos(), typeConvert(rawValue, name, f.schema()));
      }
    }

    return result;
  }

  private Object typeConvert(Object value, String name, Schema schema) throws IOException {
    if (isOptional(schema)) {
      if (value == null) {
        return null;
      } else {
        schema = getNonNull(schema);
      }
    } else if (value == null) {
      // Always fail on null for non-nullable schemas
      throw new JsonConversionException(null, name, schema);
    }

    switch (schema.getType()) {
      case BOOLEAN:
        if (value instanceof Boolean) {
          return value;
        }
        break;
      case DOUBLE:
        if (value instanceof Number) {
          return ((Number) value).doubleValue();
        }
        break;
      case FLOAT:
        if (value instanceof Number) {
          return ((Number) value).floatValue();
        }
        break;
      case INT:
        if (value instanceof Number) {
          return ((Number) value).intValue();
        }
        break;
      case LONG:
        if (value instanceof Number) {
          return ((Number) value).longValue();
        }
        break;
      case STRING:
        return value.toString();
      case ENUM:
        if (schema.getEnumSymbols().contains(value.toString())) {
          return new GenericData.EnumSymbol(schema, value.toString());
        }
        throw new JsonConversionException(String.format("Symbol %s not in enum", value.toString()),
            schema.getFullName(), schema);
      case RECORD:
        return convert((Map) value, schema);
      case ARRAY:
        Schema elementSchema = schema.getElementType();
        List listRes = new ArrayList();
        for (Object v : (List) value) {
          listRes.add(typeConvert(v, name, elementSchema));
        }
        return listRes;
      case MAP:
        Schema valueSchema = schema.getValueType();
        Map mapRes = new HashMap();
        for (Map.Entry v : ((Map) value).entrySet()) {
          mapRes.put(v.getKey(), typeConvert(v.getValue(), name, valueSchema));
        }
        return mapRes;
      default:
        throw new IllegalArgumentException(
            "JsonConverter cannot handle type: " + schema.getType());
    }
    throw new JsonConversionException(value, name, schema);
  }

  private boolean isOptional(Schema schema) {
    return schema.getType().equals(Schema.Type.UNION) &&
        schema.getTypes().size() == 2 &&
        (schema.getTypes().get(0).getType().equals(Schema.Type.NULL) ||
            schema.getTypes().get(1).getType().equals(Schema.Type.NULL));
  }

  private Schema getNonNull(Schema schema) {
    List types = schema.getTypes();
    return types.get(0).getType().equals(Schema.Type.NULL) ? types.get(1) : types.get(0);
  }

  public static class JsonConversionException extends RuntimeException {

    private Object value;
    private String fieldName;
    private Schema schema;

    public JsonConversionException(Object value, String fieldName, Schema schema) {
      this.value = value;
      this.fieldName = fieldName;
      this.schema = schema;
    }

    @Override
    public String toString() {
      return String.format("Type conversion error for field %s, %s for %s",
          fieldName, value, schema);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy