from typing import Any
from typing import Optional
from typing import Type
import json
import hashlib


def gethash(
    value: "Any",
    hashtype: str = "sha256",
    encoding: str = "utf-8",
    json_encoder: "Optional[Type[json.JSONEncoder]]" = None,
) -> str:
    """Return a hash of `value`.

    Can hash most python objects. Bytes and bytearrays are hashed directly.
    Strings are converted to bytes with the given encoding.
    All other objects are first serialised using json.

    Args:
        value: Value to hash.
        hashtype: Any of the hash algorithms supported by hashlib.
        encoding: Encoding used to convert strings to bytes before
            calculating the hash.
        json_encoder: Customised json encoder for complex Python objects.

    Returns:
        A hash of the input `value`.

    """
    if isinstance(value, (bytes, bytearray)):
        data = value
    elif isinstance(value, str):
        data = value.encode(encoding)
    else:
        # Try to serialise using json
        data = json.dumps(
            value,
            ensure_ascii=False,
            cls=json_encoder,
            sort_keys=True,
        ).encode(encoding)

    hash_ = hashlib.new(hashtype)
    hash_.update(data)
    return hash_.hexdigest()