import copy
import datetime
import io
from json import *
from typing import *

from .const import UTF_8
from .type import T


class JSON(object):

    @staticmethod
    def marshal(obj: Any, indent=None) -> Optional[str]:
        if obj is None:
            return None
        return dumps(obj, cls=Encoder, indent=indent, ensure_ascii=False)

    @staticmethod
    def unmarshal(json_str: str, clazz: Type[T]) -> T:
        dict_obj = loads(json_str)
        return clazz(dict_obj)


class Encoder(JSONEncoder):
    def default(self, o: Any) -> Any:
        if isinstance(o, io.BufferedReader):
            return o.__str__()
        if hasattr(o, "__dict__"):
            return filter_null(copy.deepcopy(vars(o)))
        if isinstance(o, datetime.datetime):
            return o.strftime("%Y-%m-%d %H:%M:%S")
        if isinstance(o, bytes):
            return str(o, encoding=UTF_8)
        if isinstance(o, int):
            return int(o)
        if isinstance(o, float):
            return float(o)
        if isinstance(o, set):
            return list(o)
        return super().default(o)


def filter_null(d: Dict) -> Dict:
    if isinstance(d, dict):
        for k, v in list(d.items()):
            if isinstance(v, dict):
                filter_null(v)
            elif v is None:
                del d[k]

    return d
