将Protobuf对象写入JSON文件

6

我有一个名为old.JSON的旧文件:

[{
    "id": "333333",
    "creation_timestamp": 0,
    "type": "MEDICAL",
    "owner": "MED.com",
    "datafiles": ["stomach.data", "heart.data"]
}]

然后我基于.proto文件创建一个对象:

message Dataset {
  string id = 1;
  uint64 creation_timestamp = 2;
  string type = 3;
  string owner = 4;
  repeated string datafiles = 6;
}

现在我想保存这个对象并将其保存回其他的.JSON文件。

我是这样做的:

import json
from google.protobuf.json_format import MessageToJson

with open("new.json", 'w') as jsfile:
    json.dump(MessageToJson(item), jsfile)

因此,我有:

"{\n  \"id\": \"333333\",\n  \"type\": \"MEDICAL\",\n  \"owner\": \"MED.com\",\n  \"datafiles\": [\n    \"stomach.data\",\n    \"heart.data\"\n  ]\n}"

如何使此文件看起来像 old.JSON 文件?

这与原始版本有何不同之处?我注意到它不在列表中,这是问题所在吗? - tdelaney
@tdelaney 是的,它不是一个列表。它有双引号而不是单引号,并且\n是显式的。 - Kenenbek Arzymatov
你直接尝试过 jsfile.write(MessageToJson(item)) 吗? - Psidom
这个列表可能是你最开始保存数据的方式。你为列表内单个dict定义了一种消息类型。从你在这里发布的内容来看,我不知道是否还为外部列表定义了另一种消息类型。但如果你只编码外层列表的每个项目,那么你就丢失了该列表。关于\n,尝试打印字符串...它们会被渲染为换行符。Python字符串的表示形式显示为\ n,因此您可以看到它们。 - tdelaney
@Psidom 它可以工作,但保存为不是列表,但我可以手动将 [] 添加到文件中。 - Kenenbek Arzymatov
看起来你正在使用两个不同的函数,它们都将Python对象转换为字符串。其中一个完成了它的工作。另一个创建了一个字符串对象的JSON转储(小心正确引用特殊字符)。选择其中一个会更好。如果我没错的话,那么你正在使用的这个JSON库会给你一个字符串,你可以直接将其写入文件。你可能应该在调试器中检查中间值。 :) - Kenny Ostrom
1个回答

7
奇怪的转义来自将文本转换为json两次,因此强制第二个调用从第一个调用中转义json字符。详细说明如下:

https://developers.google.com/protocol-buffers/docs/reference/python/google.protobuf.json_format-pysrc

31  """Contains routines for printing protocol messages in JSON format. 
32   
33  Simple usage example: 
34   
35    # Create a proto object and serialize it to a json format string. 
36    message = my_proto_pb2.MyMessage(foo='bar') 
37    json_string = json_format.MessageToJson(message) 
38   
39    # Parse a json format string to proto object. 
40    message = json_format.Parse(json_string, my_proto_pb2.MyMessage()) 
41  """ 

 89 -def MessageToJson(message, including_default_value_fields=False): 
...
 99    Returns: 
100      A string containing the JSON formatted protocol buffer message. 

这个函数会返回一个字符串类型的对象,其中包含大量的json结构。但就Python而言,它仍然只是一个字符串。
你需要将其传递给一个接受Python对象(而不是json)并将其序列化为json的函数。

https://docs.python.org/3/library/json.html

json.dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)

Serialize obj as a JSON formatted stream to fp (a .write()-supporting file-like object) using this conversion table.

好的,你要如何将一个字符串编码成json?显然不能直接使用json特殊字符,因此需要对其进行转义处理。也许有在线工具可以使用,比如http://bernhardhaeussner.de/odd/json-escape/或者http://www.freeformatter.com/json-escape.html

你可以打开这些链接,把问题中提出的json代码复制进去,让它生成正确的json代码,这样你就能得到和问题底部几乎完全相同的结果。很酷,一切正常!

不过这不是你想要的答案,因为你不想在数据结构上进行双重JSON化。你只想把它序列化为JSON格式,然后将其写入文件:

import json
from google.protobuf.json_format import MessageToJson

with open("new.json", 'w') as jsfile:
    actual_json_text = MessageToJson(item)
    jsfile.write( actual_json_text )

附加说明:MessageToJson可能需要额外的参数才能按预期工作,包括默认值字段(including_default_value_fields=True)和保留proto字段名称(preserving_proto_field_name=True)(请参见下面的注释和链接)。

是的,MessageToJson看起来很好,但它引起了新的问题。https://dev59.com/cKHia4cB1Zd3GeqPU4ni - Kenenbek Arzymatov
2
解决方案的关键部分就是将json.dump更改为jsfile.write。正如答案所指出的那样,我们不想对消息进行双重jsonify处理。 - Macrophage
现在是2021年,出现了一个新问题:MessageToJson有时会截断消息字段。请参见https://dev59.com/H2kMtIcB2Jgan1znYJEb。 - kakyo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接