json 无法编码decimal类型的对象,Decimal?

rjzwgtxy  于 2023-02-17  发布在  其他
关注(0)|答案(5)|浏览(340)

尝试将insert_one放入集合后,收到以下错误:

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

当JSON不包含decimal.Decimal对象时,代码运行良好。如果有解决方案,您可以考虑以递归的方式进行编码,以使整个python字典json_dic兼容插入MongoDB(因为json.dic条目中有不止一个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: <class 'decimal.Decimal'>

**EDIT 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
f3temu5u

f3temu5u1#

编辑:
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。

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')}
axzmvihb

axzmvihb2#

Pymongo不能识别Decimal-这就是为什么你会得到错误。
正确的pymongo插入是coll.insert_one({"number1": Decimal128('8.916')})
您还需要导入-from bson import Decimal128
现在,如果希望在不将Decimal更改为Decimal 128 '的情况下处理JSON文件,可以修改import语句。

from bson import Decimal128 as Decimal

coll.insert_one({"number1": Decimal('8.916')})
nwlls2ji

nwlls2ji3#

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()返回的上下文(注:如上面的示例代码所示)。

sauutmhj

sauutmhj4#

我建议只添加一个编解码器来在插入时自动转换数据类型。如果你递归地改变数据类型来使用Decimal128对象,你可能会破坏与现有代码的兼容性。
您可以按照教程创建一个简单的decimal.Decimal编解码器,请参阅pymongo docs here

js81xvg6

js81xvg65#

已接受的答案here部分正确,但未通过我们使用pytest编写的测试,示例数据如下:

example = {
    "string_field": "some",
    "string_array_list": ["samatta"],
    "decimal_containing_list": ["a", Decimal(8)],
    "empty_list": [],
    "empty_dict": {},
    "direct_decimal": Decimal(0),
    "nested_decimal_dict": {"very_important-field": Decimal(1)},
    "number_field": 1,
    "nested_number_field": {"very_important-field": 1},
    "number_list": [1, 2],
    "decimal_containing_field_list": [{"very_important": Decimal(1)}],
}

这里嵌套数组是这样的。我稍微改进了一下,现在的一个通过了测试:

from typing import Union
    
def convert_decimal(item: Union[dict, list]) -> Union[dict, list]:
    if item is None:
        return None

    elif isinstance(item, list):
        for index, item in enumerate(item):
            if isinstance(item, Decimal):
                item[index] = Decimal128(str(item))
            if isinstance(item, dict):
                convert_decimal(item)
        return item

    elif isinstance(item, dict):
        for k, v in list(item.items()):
            if isinstance(v, dict):
                convert_decimal(v)
            elif isinstance(v, list):
                convert_decimal(v)
            elif isinstance(v, Decimal):
                item[k] = Decimal128(str(v))

        return item

当你把它打印出来的时候

{
    "string_field": "some",
    "string_array_list": ["samatta"],
    "decimal_containing_list": ["a", Decimal128("8")],
    "empty_list": [],
    "empty_dict": {},
    "direct_decimal": Decimal128("0"),
    "nested_decimal_dict": {"very_important-field": Decimal128("1")},
    "number_field": 1,
    "nested_number_field": {"very_important-field": 1},
    "number_list": [1, 2],
    "decimal_containing_field_list": [{"very_important": Decimal128("1")}],
}

相关问题