把JSON數據格式轉換爲Python的類對象

JOSN字符串轉換爲自定義類實例對象

有時候咱們有這種需求就是把一個JSON字符串轉換爲一個具體的Python類的實例,好比你接收到這樣一個JSON字符串以下:java

{"Name": "Tom", "Sex": "Male", "BloodType": "A", "Hobbies": ["籃球", "足球"]}

我須要把這個轉換爲具體的一個Person類的實例,經過對象的方式來進行操做。在Java中有不少實現好比Gson或者FastJosn。以下代碼所示(這裏不是所有代碼,值標識最主要的部分):node

import com.alibaba.fastjson.JSONObject;
import com.example.demo.entity.Product;


String a = "{\"gmtCreate\":1559009853000,\"dataFormat\":1,\"deviceCount\":1,\"nodeType\":0,\"productKey\":\"a1U85pSQrAz\",\"productName\":\"溫度計\"}";

//JSON字符串反序列化爲一個Product對象
Product product = JSONObject.parseObject(a, Product.class);

上述這種需求通常發生在前段傳遞過來JSON字符串或者其餘系統進行RPC通訊的時候也發送過來JSON字符串,做爲接收端須要反序列化成對象來進行處理,並且Fastjson裏還有一個JSONArray.parseArray方法能夠轉換爲對象列表。但是在Python沒有像Java中這麼方便的東西。固然在Django的RESTframework框架中有這種進行序列化和反序列化的功能可使用。這裏只是對一個問題的思考,若是遇到這種狀況本身怎麼解決。python

從網上論壇中也看到過一些,不過不少都是效果有可是使用起來麻煩,因此我這裏也來講一下個人思路。編程

方式1:經過josn.loads來實現

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import json


class Person:
    def __init__(self, data=None):
        self._name = "1"
        self._sex = ""
        self._blood_type = "O"
        self._hobbies = []

        self._date_of_birth = "1900/1/1"

        if data:
            self.__dict__ = data

    # 經過屬性的方式來獲取和設置實例變量的值,若是不這樣那麼就只能經過set或者get方法來作
    @property
    def date_of_brith(self):
        return self._date_of_birth

    @date_of_brith.setter
    def date_of_brith(self, date_of_brith):
        self._date_of_birth = date_of_brith


def main():
    try:
        str1 = '{"name": "Tom", "sex": "male", "blood_type": "A", "hobbies": ["籃球", "足球"]}'
        person1 = json.loads(str1, object_hook=Person)
        print(isinstance(person1, Person))
        # 這裏你會發現沒有date_of_brith這個內容
        print(person1.__dict__)
        # 獲取date_of_brith屬性值報錯,由於JSON字符串不包含這個鍵,可是類中的實例變量有這個,正常來說你應該能夠獲取默認值,可是因爲
        # 替換了__dict__,因此就沒有了,由於__dict__本來就是實例變量的字典形式,你替換了天然也就找不到原來的了。
        # print(person.date_of_brith)

        # 下面咱們經過正常的方式實例化一個對象
        person2 = Person()
        print(person2.__dict__)
        print(person2.date_of_brith)

    except Exception as err:
        print(err)

if __name__ == "__main__":
    try:
        main()
    finally:
        sys.exit()

object_hook的含義是,默認json.loads()返回的是dict,你可使用object_hook來讓其返回其餘類型的值,它這裏實現的原理就是把你傳遞進來的JSON字符串傳遞給了object_hook指定的方法或者類(若是是類的話則會執行__init__方法,其實就是實例化),這時候在類的__init方法中咱們經過賦值給self.dict__,其實這就等於對Person類的實例變量作了替換,除非你的JSON字符串的鍵和實例變量的名稱以及數量一致不然你沒法經過你在類裏定義的實例變量名稱獲取經過JSON字符串傳遞進去的值。以下圖:json

因此經過上面能夠看出來,這個過程不是爲實例變量賦值的過程而是一個替換的過程,Python是動態語言這一點和JAVA不一樣。若是你在程序中用單下劃線標識變量爲私有(只是規範而不是真正的私有)那麼你傳遞的JSON字符串的鍵也須要有下劃線,這樣你經過實例的方法才能獲取。既然額外增長下劃線不太現實,那麼有沒有其餘辦法呢?看方式2框架

方式2:經過反射機制來實現

先看一下類的定義code

#!/usr/bin/env python
# -*- coding: utf-8 -*-


class Person:
    def __init__(self):
        self._name = "1"
        self._sex = ""
        self._blood_type = "O"
        self._hobbies = []
        self._date_of_birth = "1900/1/1"

    def __str__(self):
        """
        輸出實例的類名字,而不是一個地址
        :return: 該實例的類名字
        """
        return self.__class__.__name__

    # 當一個方法加上這個裝飾器以後,hasattr()中的屬性要寫成這個方法的名稱,而不是實例變量的名稱。
    # 若是不加這個裝飾器,那麼hasattr()中的屬性名稱要和實例變量的名稱保持一致
    @property
    def Name(self):
        return self._name

    @Name.setter
    def Name(self, name):
        self._name = name

    @property
    def Sex(self):
        return self._sex

    @Sex.setter
    def Sex(self, sex):
        self._sex = sex

    @property
    def BloodType(self):
        return self._blood_type

    @BloodType.setter
    def BloodType(self, blood_type):
        self._blood_type = blood_type

    @property
    def Hobbies(self):
        return self._hobbies

    @Hobbies.setter
    def Hobbies(self, hobbies):
        self._hobbies = hobbies

    @property
    def date_of_brith(self):
        return self._date_of_birth

    @date_of_brith.setter
    def date_of_brith(self, date_of_brith):
        self._date_of_birth = date_of_brith

下面看看轉換的方法orm

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import json
import importlib


def get_instance(str_stream, class_full_path=None):
    """
    :param str_stream: json的字符串形式 '{"Name": "Tom", "Sex": "Male", "BloodType": "A"}'
    :param class_full_path: package.module.class
    :return:
    """
    try:
        json_obj = json.loads(str_stream)
    except Exception as err:
        print("輸入的字符串不符合JSON格式,請檢查。")
        return None

    if class_full_path is None:
        return json_obj
    else:
        try:
            # 獲取模塊路徑
            module_path = ".".join(class_full_path.split(".")[0:-1])
            # 獲取類名稱
            class_name = class_full_path.split(".")[-1]
            # 經過模塊名加載模塊
            CC = importlib.import_module(module_path)

            # 判斷是否有class_name所表明的屬性
            if hasattr(CC, class_name):
                # 獲取模塊中屬性
                temp_obj = getattr(CC, class_name)
                # 實例化對象
                obj = temp_obj()

                for key in json_obj.keys():
                    obj.__setattr__(key,  json_obj[key])

                return obj
            else:
                pass
        except Exception as err:
            print(err)
            return None


def main():
    try:
        str1 = '{"Name": "Tom", "Sex": "Male", "BloodType": "A", "Hobbies": ["籃球", "足球"]}'
        person1 = get_instance(str1, class_full_path="AAA.Classes.Person")
        # 查看類型
        print(type(person1))
        # 查看屬性
        print(person1.__dict__)
        # 查看指定屬性
        print(person1.Name)

    except Exception as err:
        print(err)

if __name__ == "__main__":
    try:
        main()
    finally:
        sys.exit()

__import__() 有2個參數,第一個是類,第二個是fromlist,若是不寫fromlist,則按照下面的寫法會只導入AAA包,若是fromlist有值則會導入AAA下面的Classes模塊cc = __import__("AAA.Classes", fromlist=True)不寫fromlist 至關於 import AAA ,若是寫了就至關因而from AAA import Classes編程時若是使用動態加載建議使用importlib.import_module(),而不是__import__()對象

下面看一下效果:blog

能夠看到,這樣操做以後就是給實例變量賦值而不是像以前那樣的替換,並且保留了類中實例變量的私有規範。不過須要說明的是JSON字符串中的鍵名稱要和類裏面定義的屬性名稱同樣,也就是鍵名稱要和類中@property裝飾的方法同名。咱們也能夠看到這種使用方式也有默認JSONObject.parseObject的意思。

不過這只是一個簡單的實現,只能經過單一JSON字符串生成對象不能生成對象列表。固然有興趣的朋友能夠本身根據這個思路進行擴展。

另外既然不管是loads仍是我本身的方法搜須要保證JSON字符串的鍵和變量名稱一致你們就不要糾結於名稱一致的問題,可是個人方法作到了保持實例變量命名、操做實例屬性時候的規範,同時對類也沒有過多的入侵性而不像loads方法中還須要在類的init方法裏面增長沒必要要的內容。在我這個方法中若是能實現忽略大小寫通用性就更好了。歡迎你們來提供思路。

相關文章
相關標籤/搜索