class ObjectBuilder {

    @SuppressWarnings("unchecked")
    public static <T extends Map<String, ?>> T createObject(Class<T> mapClass, Type genericType, JsonValue jsonObject)
            throws JsonException {
        if (jsonObject == null || jsonObject.isNull()) {
            return null;
        }
        Class mapValueClass;
        Type mapValueType;
        if (genericType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType)genericType;
            if (!String.class.isAssignableFrom((Class)parameterizedType.getActualTypeArguments()[0])) {
                throw new JsonException("Key of Map must be String. ");
            }
            mapValueType = parameterizedType.getActualTypeArguments()[1];
            if (mapValueType instanceof Class) {
                mapValueClass = (Class)mapValueType;
            } else if (mapValueType instanceof ParameterizedType) {
                mapValueClass = (Class)((ParameterizedType)mapValueType).getRawType();
            } else {
                throw new JsonException(
                        String.format("This type of Map can't be restored from JSON source.\nMap is parameterized by wrong Type: %s",
                                      parameterizedType));
            }
        } else {
            throw new JsonException("Map is not parameterized. Map<Sting, ?> is not supported.");
        }
        Constructor<? extends T> constructor;
        if (mapClass.isInterface() || Modifier.isAbstract(mapClass.getModifiers())) {
            constructor = getConstructor(findAcceptableMapImplementation(mapClass), Map.class);
        } else {
            constructor = getConstructor(mapClass, Map.class);
        }

        Types jsonMapValueType = getType(mapValueClass);
        HashMap<String, Object> sourceMap = new HashMap<>(jsonObject.size());
        Iterator<String> keys = jsonObject.getKeys();
        while (keys.hasNext()) {
            String key = keys.next();
            JsonValue childJsonValue = jsonObject.getElement(key);
            if (jsonMapValueType == null) {
                sourceMap.put(key, createObject(mapValueClass, childJsonValue));
            } else {
                switch (jsonMapValueType) {
                    case BYTE:
                    case SHORT:
                    case INT:
                    case LONG:
                    case FLOAT:
                    case DOUBLE:
                    case BOOLEAN:
                    case CHAR:
                    case STRING:
                    case NULL:
                    case ARRAY_BYTE:
                    case ARRAY_SHORT:
                    case ARRAY_INT:
                    case ARRAY_LONG:
                    case ARRAY_FLOAT:
                    case ARRAY_DOUBLE:
                    case ARRAY_BOOLEAN:
                    case ARRAY_CHAR:
                    case ARRAY_STRING:
                    case CLASS:
                        sourceMap.put(key, createObjectKnownTypes(mapValueClass, childJsonValue));
                        break;
                    case ARRAY_OBJECT:
                        sourceMap.put(key, createArray(mapValueClass, childJsonValue));
                        break;
                    case COLLECTION:
                        sourceMap.put(key, createCollection(mapValueClass, mapValueType, childJsonValue));
                        break;
                    case MAP:
                        sourceMap.put(key, createObject(mapValueClass, mapValueType, childJsonValue));
                        break;
                    case ENUM:
                        sourceMap.put(key, createEnum(mapValueClass, childJsonValue));
                        break;
                }
            }
        }
        try {
            return constructor.newInstance(sourceMap);
        } catch (Exception e) {
            throw new JsonException(e.getMessage(), e);
        }
    }

    private  ObjectBuilder();

}