【问题标题】:How to inspect mystery deserialized object in Python如何在 Python 中检查神秘的反序列化对象
【发布时间】:2011-04-11 23:54:29
【问题描述】:

我正在尝试将 JSON 加载回对象中。 “加载”方法似乎可以正常工作,但该对象似乎没有我期望的属性。

如何检查/检查我拥有的对象(这是基于 Web 的代码)。

  results = {"Subscriber": {"firstname": "Neal", "lastname": "Walters"}}
  subscriber = json.loads(results)


  for item in inspect.getmembers(subscriber): 
     self.response.out.write("<BR>Item")
     for subitem in item: 
         self.response.out.write("<BR>&nbsp;SubItem=" + subitem)

上面的尝试返回了这个:

   Item
     SubItem=__class__

我认为这并不重要,但对于上下文: JSON 实际上来自 Google App Engine 中的 urlfetch 使用此实用程序创建的 REST Web 服务: http://code.google.com/p/appengine-rest-server。 正在使用以下定义从数据存储中检索数据:

class Subscriber(db.Model):
    firstname    = db.StringProperty()
    lastname     = db.StringProperty()

谢谢, 尼尔

更新 #1:基本上我试图将 JSON 反序列化回一个对象。 理论上它是从一个对象序列化的,我现在想把它放回一个对象中。 也许更好的问题是如何做到这一点?

更新#2:我试图将一个复杂的程序抽象为几行代码,所以我在“伪编码”中犯了一些错误,以便在此处发布。

这是一个更好的代码示例,现在从我可以在 PC 上运行的网站中取出。

results = '{"Subscriber": {"firstname": "Neal", "lastname": "Walters"}}'
subscriber = json.loads(results)
for key, value in subscriber.items():
    print " %s: %s" %(key, value)

上面的运行,它显示的内容看起来并不比 JSON 字符串本身更结构化。它显示这个: 订阅者:{u'lastname': u'Walters', u'firstname': u'Neal'}

我有更多的微软背景,所以当我听到序列化/反序列化时,我想从一个对象到一个字符串,然后从一个字符串回到一个对象。因此,如果我序列化为 JSON,然后反序列化,我会得到什么,字典、列表或对象?实际上,我从 REST webmethod 获取 JSON,这是代表我为我序列化我的对象。

理想情况下,我想要一个与上面的订阅者类匹配的订阅者对象,理想情况下,我不想编写一次性自定义代码(即特定于“订阅者”的代码),因为我想做其他几十个班级也是如此。如果我必须编写一些自定义代码,我将需要通用地编写它,以便它适用于任何类。

更新#3:这是为了更多地解释为什么我认为这是一个必要的工具。我正在编写一个巨大的应用程序,可能在 Google App Engine (GAE) 上。我们倾向于 REST 架构有几个原因,但其中一个原因是我们的 Web GUI 应该通过 REST Web 层访问数据存储。 (我更习惯于 SOAP,所以切换到 REST 本身就是一个小挑战)。因此,获取和更新数据的经典方法之一是通过业务或数据层。通过使用上面提到的 REST 实用程序,我可以选择 XML 或 JSON。我希望在我们开发大型应用程序之前做一个小型的工作原型)。然后,假设我们有一个成功的应用程序,GAE 将它的价格翻了一番。然后我们可以只重写数据层,使用我们的 Python/Django 用户层(Web 代码),并在 Amazon 或其他地方运行它。

如果我要做所有这些,为什么我希望一切都是字典对象。难道我不想拥有成熟的阶级结构的力量吗?下一个技巧之一是某种对象关系映射 (ORM),因此我们不必公开我们的确切数据表,而是更多的逻辑层。

我们还希望向可能使用任何语言的付费用户公开一个 RESTful API。对于他们来说,他们可以使用 XML 或 JSON,他们不会使用这里讨论的序列化例程。

【问题讨论】:

  • loads() 不应该传递一个字符串吗?

标签: python json google-app-engine inspection


【解决方案1】:

json 只编码字符串、浮点数、整数、javascript 对象(python 字典)和列表。

您必须创建一个函数将返回的字典转换为一个类,然后使用 object_hook 关键字参数和 json 字符串将其传递给 json.loads。下面是一些充实的代码:

import json

class Subscriber(object):
    firstname = None
    lastname = None


class Post(object):
    author = None
    title = None


def decode_from_dict(cls,vals):
    obj = cls()
    for key, val in vals.items():
        setattr(obj, key, val)
    return obj


SERIALIZABLE_CLASSES = {'Subscriber': Subscriber,
                        'Post': Post}

def decode_object(d):
    for field in d:
        if field in SERIALIZABLE_CLASSES:
            cls = SERIALIZABLE_CLASSES[field]
            return decode_from_dict(cls, d[field])
    return d


results = '''[{"Subscriber": {"firstname": "Neal", "lastname": "Walters"}},
              {"Post": {"author": {"Subscriber": {"firstname": "Neal",
                                                  "lastname": "Walters"}}},
                        "title": "Decoding JSON Objects"}]'''
result = json.loads(results, object_hook=decode_object)
print result
print result[1].author

这将处理任何可以在没有构造函数参数的情况下实例化并且setattr 可以工作的类。

另外,这使用json。我对simplejson 没有经验,所以 YMMV 但我听说它们是相同的。

请注意,虽然两个订阅者对象的值相同,但生成的对象却不同。这可以通过记忆decode_from_dict 类来解决。

【讨论】:

  • 看起来很有趣,但如果该类有 10-30 个或更多属性,我几乎需要一个代码生成器来制作 init 方法。这就是为什么我沿着“检查”的思路思考的原因。我正在使用 Google App Engine 的 db.model 类(不确定这有什么区别),但每个类基本上都是一个数据库表/行,每个类通常包含 20-50 个属性/列。明天我会尝试更多地浏览您的代码,今晚有点精疲力尽。谢谢。
  • @Neal,如果您想要将嵌套的字典、列表、字符串和数字组合成一些奇怪的不同类型的嵌套对象(而不是显而易见的方法:嵌套字典、列表、字符串,和数字),你必须进一步指定你的元数据(各种类型的实际嵌套字典和c要伪装?)显然也在序列化流中(所以除了你的代码之外,没有人的代码能够服务这样的流,只有你自己才能解码它们:universe 除了 REST 和 JSON 的核心思想、简单性和互操作性)。
  • @Neal,我更新了它以适应您所描述的情况。您只需为希望能够序列化/反序列化为 json 的每个类创建一个字典条目。
  • @Alex 如果您查看代码,只需 13 行代码就可以工作,并且它使用了一种非常简洁的格式来编码对象。我几乎不会认为这很复杂,并且在另一个协议之上构建一个协议在实践中是相当糟糕的。如果我需要解码那个 json,13 行代码不会阻止我。
  • @Aaron,问题不在于 13 行——而是由代理进行编码和代理执行解码。此类专有元数据需要双方共同了解 JSON 和 REST 标准之外的知识,这与 REST 和 JSON 建立成功和不断增长的思想份额的核心概念背道而驰(而 XML 和 SOAP 中这种常见的粗略分层可以说是为什么 JSON 和 REST 更轻且 免费 从这种“厚胶水”中获得,如今通常比它们更受欢迎)。
【解决方案2】:

results 在您的 sn-p 中是一个字典,而不是一个字符串,因此 json.loads 会引发异常。如果这是固定的,则内部循环中的每个 subitem 都是一个元组,因此尝试将其添加到字符串中会引发 another 异常。我猜你已经简化了你的代码,但是这两种类型的错误应该已经表明你简化了它太多(而且不正确)。为什么不使用(同样简化的)working sn-p,以及您想要json.loads 的实际字符串,而不是使用不可能重现您的问题的字符串?这样的做法会让你更容易为你提供帮助。

除了查看实际的字符串并显示一些明显的信息(例如type(subscriber))之外,很难根据那个明显损坏的代码和如此不充分的信息提供更多帮助:-(。

编辑:在“update2”中,OP 说

It displays this: Subscriber: {u'lastname': u'Walters', u'firstname': u'Neal'}

...还有什么其他可能显示,祈祷?!您将键打印为字符串,然后将值打印为字符串——键 is 是一个字符串,而值是另一个字典,所以它当然是“字符串化的”(JSON 中的所有字符串都是Unicode——就像在 C# 或 Java 中一样,你说你来自 MSFT 背景,那么为什么这会让你感到惊讶呢?!)。 str(somedict),与repr(somedict) 相同,显示键和值的repr(用大括号括起来,冒号和逗号作为适当的分隔符)。

JSON,一种完全独立于语言的序列化格式,虽然最初以 Javascript 为中心,但完全不知道您希望看到哪些类(如果有的话)实例(当然它没有' t,并且认为它可能会是荒谬的:如果它对“类”的概念进行硬编码,那么它怎么可能与语言无关,包括 Javascript,甚至没有?!)——所以它使用(用 Python 术语)字符串、数字、列表和字典(任何半体面的现代语言都可以预期的四种非常基本的数据类型如果没有正确嵌入语言,至少在某些库中!)。当您json.loads 一个字符串时,您将总是得到上述四种数据类型的某种嵌套组合(所有字符串都是 unicode,所有数字都是浮点数,顺便说一句;-)。

如果您不知道(并且不想通过某种任意约定或其他方式进行编码)正在序列化哪个类的实例,但绝对必须返回类实例(不仅仅是字典等)当您反序列化时,JSON 本身无法帮助您——元信息不可能出现在 JSON 序列化字符串本身中。

如果您对四种基本类型没问题,并且只想查看一些您认为比相关基本类型的默认 Python 字符串打印“更漂亮”的打印结果,那么您必须编写自己的递归代码漂亮的打印功能取决于您对“漂亮”的主观定义(我怀疑您是否喜欢 Python 自己的 pprint 标准库模块而不是您喜欢当前结果;-)。

【讨论】:

  • @Neal,当然,但我很难理解您还有什么期望——相应地编辑我的答案。
  • 对我来说很明显,如果你从一个对象开始,然后序列化/反序列化,你会希望得到你开始的同一个对象,而不是那个对象的字典表示。我会更好地使用 XML 进行序列化吗?我正在试验的 REST 服务器可以返回 JSON 或 XML。是的,JSON 是独立于语言的,但 SimpleJson 是特定于 Python 的,对吧?似乎它的 .load 会检查并重新创建对象(如果有传递类名的方法)。我想要做的事情在 C# 中非常简单明了,但我仍在学习 Python 的来龙去脉。
  • 我的 C# 类比:biztalk-training.com/articles.php?article_id=8 和 update2 中的打印输出遵循 David 的另一个答案的示例代码。
  • 我也很好奇这个。看来我的回答符合他的要求。
  • @Neal,REST 可以提供 XML 或 JSON,但独立于类名和结构等细节以及用于查询 AND 以提供数据的语言。您的 C# 示例依赖于序列化器和反序列化器使用相同的语言(或具有同等能力的语言 - 例如,Python 和 C++ 具有多重继承,Java 和 C# 有,因此前两者之间不可能互操作)后两者!!!)都知道关于被编码和解码的类的所有细节——所有这些都难以置信地与 REST 不兼容,令人难以置信。
【解决方案3】:

我的猜测是loads 正在返回一个字典。要迭代其内容,请使用以下内容:

for key, value in subscriber.items():
    self.response.out.write("%s: %s" %(key, value))

【讨论】: