【问题标题】:Pymongo: Cannot encode object of type decimal.Decimal?Pymongo:无法编码十进制类型的对象。十进制?
【发布时间】:2020-08-10 21:13:26
【问题描述】:

尝试将insert_one 放入集合后。 我收到此错误:

bson.errors.InvalidDocument: cannot encode object: Decimal('0.16020'), of type: <class 'decimal.Decimal'>

当 JSON 不包含 decimal.Decimal 对象时,代码运行良好。如果有解决方案,您是否可以考虑以递归方式对其进行编码,以使整个 python 字典 json_dic 兼容以插入到 MongoDB 中(因为在 @987654327 中有不止一次的类 decimal.Decimal 的实例@条目)。

任何帮助将不胜感激!

编辑 1:这是我正在处理的 json

import simplejson as json
from pymongo import MongoClient

json_string = '{"A" : {"B" : [{"C" : {"Horz" : 0.181665435,"Vert" : 0.178799435}}]}}'

json_dict = json.loads(json_string)
this_collection.insert_one(json_dict)

这会产生 bson.errors.InvalidDocument: cannot encode object: Decimal('0.181665435'), of type: &lt;class 'decimal.Decimal'&gt;

编辑 2: 不幸的是,我上面的示例过于简化了我现有的 JSON,@Belly Buster 提供的答案(尽管上面的 Json 可以正常工作)出现错误:

AttributeError: 'decimal.Decimal' object has no attribute 'items'

使用我的实际 Json,所以我在这里提供完整的 Json,希望能找出问题所在(也是 as a screen-shot):

json_string = 
'
{
  "Setting" : {
    "GridOptions" : {
      "Student" : "HighSchool",
      "Lesson" : 1,
      "Attended" : true
    },
    "Grades" : [
      80,
      50.75
    ],
    "Count" : 2,
    "Check" : "Coursework",
    "Passed" : true
  },
  "Slides" : [
    {
      "Type" : "ABC",
      "Duration" : 1.5
    },
    {
      "Type" : "DEF",
      "Duration" : 0.5
    }
  ],
  "Work" : {
    "Class" : [
      {
        "Time" : 123456789,
        "Marks" : {
          "A" : 50,
          "B" : 100
        }
      }
    ],
    "CourseWorkDetail" : [
      {
        "Test" : {
          "Mark" : 0.987654321
        },
        "ReadingDate" : "Feb162006",
        "Reading" : 300.001,
        "Values" : [
          [
            0.98765
          ],
          [
            -0.98765
          ]
        ]
      },
      {
        "Test" : {
          "Mark" : 0.123456789
        },
        "ReadingDate" : "Jan052010",
        "Reading" : 200.005,
        "Values" : [
          [
            0.12345
          ],
          [
            -0.12345
          ]
        ]
      }
    ]
  },
  "listing" : 5
}
'

编辑 3: 作为对下面答案的补充,您可以像这样在字典中递归迭代并使用答案中的函数

def iterdict(dict_items, debug_out):
    for k, v in dict_items.items():
        if isinstance(v):
            iterdict(v)
        else:
            dict_items[k] = convert_decimal(v)
    return dict_items

【问题讨论】:

  • 请使用您尝试过的代码更新您的帖子。
  • 我已经添加了代码
  • 您添加的代码在 mongo 4.2 版上运行没有错误。你运行的是什么版本?您是否运行了确切的代码(没有看到任何连接线)?

标签: python json mongodb decimal pymongo


【解决方案1】:

我建议只添加一个编解码器以在插入时自动转换数据类型。如果您递归地更改数据类型以使用 Decimal128 对象,您可能会破坏与现有代码的兼容性。

您可以按照教程在 pymongo 文档here 中创建一个简单的decimal.Decimal 编解码器

【讨论】:

    【解决方案2】:
    from bson.decimal128 import Decimal128, create_decimal128_context
    from decimal import localcontext
    
    decimal128_ctx = create_decimal128_context()
    with localcontext(decimal128_ctx) as ctx:
        horiz_val = Decimal128(ctx.create_decimal("0.181665435"))
        vert_val = Decimal128(ctx.create_decimal("0.178799435"))
    
    doc = { 'A': { 'B': [ { 'C': { 'Horiz': horiz_val, 'Vert': vert_val } } ] } }
    result = collection.insert_one(doc)
    # result.inserted_id
    
    pprint.pprint(list(collection.find()))
    
    [ {'A': {'B': [{'C': {'Horiz': Decimal128('0.181665435'),
                          'Vert': Decimal128('0.178799435')}}]},
      '_id': ObjectId('5ea79adb915cbf3c46f5d4ae')} ]
    

    注意事项:

    来自 PyMongo 的 decimal128 文档:

    为了确保计算结果始终可以存储为 BSON Decimal128 使用create_decimal128_context() 返回的上下文(注意:如上面的示例代码所示)。

    【讨论】:

    • 感谢 prasad_,如果我构造 Json 变量但我在文件中给出它,则此代码工作正常,因此我需要在导入后迭代更改其中的值。 Belly Buster 的回答提供了一个功能,但它不适用于我更长更复杂的 Json。我已经更新了问题以包含更长的 Json。有没有一个函数可以迭代更新字符串 Json 中与 MongoDB 不兼容的值?
    【解决方案3】:

    编辑:

    convert_decimal() 函数将在复杂的 dict 结构中执行 Decimal 到 Decimal128 的转换:

    import simplejson as json
    from pymongo import MongoClient
    from decimal import Decimal
    from bson.decimal128 import Decimal128
    
    def convert_decimal(dict_item):
        # This function iterates a dictionary looking for types of Decimal and converts them to Decimal128
        # Embedded dictionaries and lists are called recursively.
        if dict_item is None: return None
    
        for k, v in list(dict_item.items()):
            if isinstance(v, dict):
                convert_decimal(v)
            elif isinstance(v, list):
                for l in v:
                    convert_decimal(l)
            elif isinstance(v, Decimal):
                dict_item[k] = Decimal128(str(v))
    
        return dict_item
    
    db = MongoClient()['mydatabase']
    json_string = '{"A" : {"B" : [{"C" : {"Horz" : 0.181665435,"Vert" : 0.178799435}}]}}'
    json_dict = json.loads(json_string, use_decimal=True)
    db.this_collection.insert_one(convert_decimal(json_dict))
    print(db.this_collection.find_one())
    

    给予:

    {'_id': ObjectId('5ea743aa297c9ccd52d33e05'), 'A': {'B': [{'C': {'Horz': Decimal128('0.181665435'), 'Vert': Decimal128('0.178799435')}}]}}
    

    原文:

    要将小数转换为 MongoDB 满意的 Decimal128,请将其转换为字符串,然后再转换为 Decimal128。这个 sn-p 可能会有所帮助:

    from pymongo import MongoClient
    from decimal import Decimal
    from bson.decimal128 import Decimal128
    
    db = MongoClient()['mydatabase']
    your_number = Decimal('234.56')
    your_number_128 = Decimal128(str(your_number))
    db.mycollection.insert_one({'Number': your_number_128})
    print(db.mycollection.find_one())
    

    给予:

    {'_id': ObjectId('5ea6ec9b52619c7b39b851cb'), 'Number': Decimal128('234.56')}
    

    【讨论】:

    • 谢谢Belly,我得到了Json 字符串,我没有在旅途中构建它,有没有办法可以将您的答案应用于现有字符串?我已经用我的实际 Json 模式更新了原始问题。非常感谢!
    • 感谢您提供完美运行的功能。在我的愚蠢中,我认为我可以提供一个关于实际 Json 是什么的小图(你的代码可以完美地工作),但是你愿意看看我更新了问题的更大的 Json。出于某种原因,在该 Json 中递归迭代时,您的函数会抛出此错误:AttributeError: 'decimal.Decimal' object has no attribute 'items' 请您看看如何更新您的函数以在这个较长的 Json 字符串上执行相同的操作?再次感谢!
    • 查找一个函数,该函数将递归嵌套的 dict 结构并结合使用它和我的答案。将是一次很好的学习体验!
    • 感谢@Belly Buster
    【解决方案4】:

    Pymongo 无法识别 Decimal - 这就是您收到错误的原因。

    正确的 pymongo 插入是 coll.insert_one({"number1": Decimal128('8.916')})

    您还需要导入 - from bson import Decimal128

    现在,如果您想在不将 Decimal 更改为 Decimal128 的情况下处理您的 JSON 文件,您可以修改导入语句。

    from bson import Decimal128 as Decimal
    
    coll.insert_one({"number1": Decimal('8.916')})
    

    【讨论】:

    • 谢谢戴夫,我想动态插入他的 Json 数据,但我正在使用现有的 Json 字符串。有没有办法可以递归地将您的答案应用于现有字符串?我现在已经用我的实际 Json 模式更新了原始问题。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2016-04-17
    • 1970-01-01
    • 1970-01-01
    • 2020-04-03
    • 1970-01-01
    • 2013-11-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多