英文:
Writing to SQL yields different results with shell / django
问题
class myJSONField(models.JSONField):
def get_prep_value(self, v):
if v is None:
return value
return json.dumps(v, ensure_ascii=False)
class Product(models.Model):
data = myJSONField()
number = models.PositiveIntegerField()
Product.create(number=1, data={"direction": "süd"})
产生的结果:
{"direction": "süd"}
在使用 sqlite3 进行测试时:
conn = sqlite3.connect('db.sqlite3')
c = conn.cursor()
test = json.dumps({"direction": "Süd"}, ensure_ascii=False)
sql = """UPDATE test_product SET data = ? WHERE id=1"""
c.execute(sql, [test])
conn.commit()
产生的结果:
{"direction": "süd"}
我使用相同的 db.sqlite3
文件进行了两次测试。
- 我不理解为什么 Django 逻辑会忽略
ensure_ascii = False
标志。 - 如何在我的 JSONField 中获取正确的 JSON(没有转义的
"
)。
编辑:我可以在我的 Django 管理界面中看到结果,我还在 SQLite 中测试了省略 ensure_ascii = False
设置的情况:
a = {"direction": "Süd", }
b = json.dumps(a)
sql = """UPDATE test_product SET data = ? WHERE id=1"""
c.execute(sql, [b])
conn.commit()
结果是:
{"direction": "Süd"}
(注意:这个结果没有包含 "
!)
当通过 Django 写入数据库并通过 python manage.py shell
和 sqlite3
直接读取时:
import sqlite3
conn = sqlite3.connect('db.sqlite3')
c = conn.cursor()
c.execute('SELECT * FROM virtualShelf_product WHERE id=1')
res = c.fetchall()
[(1, 1, '"{"direction": "süd"}"')]
当通过 Django ORM 读取同一产品时:
p = Product.objects.get(id=1)
p.data
'{"direction": "süd"}'
英文:
I have a simple model:
class myJSONField(models.JSONField):
def get_prep_value(self, v):
if v is None:
return value
return json.dumps(v, ensure_ascii = False)
class Product(models.Model):
data = myJSONField()
number = models.PositiveIntegerField()
Product.create(number = 1, data = {"direction": "süd"})
yields:
"{\"direction\": \"s\u00fcd\"}"
Testing with sqlite3:
conn = sqlite3.connect('db.sqlite3')
c = conn.cursor()
test = json.dumps({"direction": "Süd"}, ensure_ascii = False)
sql = ("""UPDATE test_product SET data = ? WHERE id=1""")
c.execute(sql, [test])
conn.commit()
yields:
{"direction": "süd"}
I am using the same db.sqlite3
file for both tests.
- I do not understand why the
ensure_ascii = False
flag is ignored by the django logic. - How can I obtain a correct JSON in my JSONField (without the escaped
"
)?
Edit: I can see the result in my django admin and I also tested omitting the ensure_ascii = False
setting in SQLite:
a = {"direction": "Süd", }
b = json.dumps(a)#, ensure_ascii = False)
sql = ("""UPDATE test_product SET data = ? WHERE id=1""")
c.execute(sql, [b])
conn.commit()
which leads to:
{"direction": "S\u00fcd"}
(remark: no "
around that one!)
When writing to the database with Django and reading via python manage.py shell
and sqlite3
directly:
import sqlite3
conn = sqlite3.connect('db.sqlite3')
c = conn.cursor()
c.execute('SELECT * FROM virtualShelf_product WHERE id=1')
res = c.fetchall()
[(1, 1, '"{\\"direction\\": \\"s\\u00fcd\\"}"')]
when reading the same product via Django ORM:
p = Product.objects.get(id=1)
p.data
'{"direction": "süd"}'
答案1
得分: 1
通过覆盖get_prep_value
方法,您进行了两次序列化。首先是在您的myJSONField
中,然后是由get_db_prep_value
函数进行的第二次序列化,该函数使用get_prep_value
作为辅助函数。
实际上,如果我们检查源代码(GitHub),我们可以看到以下内容:
def get_db_prep_value(self, value, connection, prepared=False):
if not prepared:
value = self.get_prep_value(value)
# ...
return connection.ops.adapt_json_value(value, self.encoder)
根据连接本身的不同,Django可以发送例如BSON数据块或JSON数据,除了PostgreSQL,它在这里对数据进行编码:
def adapt_json_value(self, value, encoder):
return json.dumps(value, cls=encoder)
如果您覆盖get_prep_value
,它会将其转换为字符串,然后再次对其进行JSON序列化。
您可以实现一个自定义后端,传递ensure_ascii=False
,或者一个简单的解决方法是通过monkey patching注入这个参数。例如,在AppConfig
中:
# app_name/apps.py
from django.apps import AppConfig
import json
class AppConfig(AppConfig):
name = "app_name"
def ready(self):
def adapt_json_value(self, value, encoder):
return json.dumps(value, ensure_ascii=False, cls=encoder)
from django.db.backends.base.operations import BaseDatabaseOperations
BaseDatabaseOperations.adapt_json_value = adapt_json_value
当然,然后我们可以省略myJSONField
中的get_prep_value
,这实际上使myJSONField
相对于JSONField
失去了作用。
英文:
By overriding get_prep_value
, you are serializing twice. Indeed, once in your myJSONField
, and a second time by the get_db_prep_value
function which uses get_prep_value
as a helper function.
Indeed, if we inspect the source code <sup>[GitHub]</sup>, we see:
> <pre><code>def get_db_prep_value(self, value, connection, prepared=False):
> if not prepared:
> value = self.get_prep_value(value)
> # …
> return <b>connection.ops.adapt_json_value(value, self.encoder)</b></code></pre>
depending on the connection itself, Django can then send for example a BSON blob, or a JSON. Except for PostgreSQL, it encodes the data with <sup>[GitHub]</sup>:
<pre><code>def adapt_json_value(self, value, encoder):
return <b>json.dumps(</b>value, cls=encoder<b>)</b></code></pre>
if you override get_prep_value
, it converts it to a string, and then you JSON serialize it a second time.
One can implement a custom backend that would pass ensure_ascii=False
, a poor man's solution would be to just inject this through monkey patching. For example in the AppConfig
:
<pre><code># <em>app_name</em>/apps.py
from django.apps import AppConfig
class AppConfig(AppConfig):
name = "<em>app_name</em>"
def ready(self):
def adapt_json_value(self, value, encoder):
return json.dumps(value, <b>ensure_ascii=False</b>, cls=encoder)
from django.db.backends.base.operations import BaseDatabaseOperations
BaseDatabaseOperations<b>.adapt_json_value = adapt_json_value</b></code></pre>
of course we then omit the get_prep_value
in the myJSONField
, which also basically nullifies myJSONField
over JSONField
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论