I have a Django Application and want to convert a value from a string field which is comma separated to a key vaule pair and add it to a json data block.
class MyClass1(models.Model):
keywords = models.TextField(_('Keywords'), null=True, blank=True)
Example of list:
blue,shirt,s,summer,for women
The JSON data in my code
data = {
"name": self.name,
"type": self.type,
...
"keywords": []
}
I want to split the comma separated string of self.keywords and append it to the keywords field in my json, but as a array like this:
{
"name": keyword,
},
I do the split with the split function, but dont know how to create a key value pair as array and append to keywords.
Expected output:
data = {
"name": "Name of item",
"type": "Type of item",
...
"keywords": [
{
"name": "blue"
},
{
"name": "shirt"
},
...
]
}
You can work with .split():
data = {
'name': self.name,
'type': self.type,
# …
'keywords': [{'name': n} for n in self.keywords.split(',')],
}
It might however be better to work with a custom field. You can define such field with:
from django.db import models
class ListAsCharField(models.Field):
def __init__(self, separator=',', *args, **kwargs):
self.separator = separator
super().__init__(*args, **kwargs)
def get_db_prep_value(self, value, connection, prepared=False):
if isinstance(value, (str, type(None))):
value = self.separator.join(str(x) for x in value)
return super().get_db_prep_value(value, connection, prepared)
def from_db_value(self, value, expression, connection):
if isinstance(value, str):
return value.split(self.separator)
def to_python(self, value):
if isinstance(value, str):
value = value.split(self.separator)
return value
then you can use this in the model to automatically do the wrapping/unwrapping from a list:
class MyClass1(models.Model):
keywords = ListAsCharField(
max_length=256, verbose_name=_('Keywords'), null=True, blank=True
)
Then you can process this with:
data = {
'name': self.name,
'type': self.type,
# …
'keywords': [{'name': n} for n in self.keywords],
}
Related
Hi I made my dto something like this
class MyRequestDto(ma.Schema):
#pre_load
def wrap_data(self, in_data, **kwargs):
return {"rooms": in_data}
rooms = ma.Dict(ma.String, ma.Dict(ma.Integer, ma.String))
and I want to send request something like this :
{
"1faf8f07-2977-180e-7bc2-b5adf8badasda": {"student_id":11210687,"room_id":"100"}
}
but getting error like this
{
"rooms": {
"1faf8f07-2977-180e-7bc2-b5adf8badasda": {
"value": {
"student_id": {
"key": [
"Not a valid integer."
]
},
"room_id": {
"key": [
"Not a valid integer."
]
}
}
}
}
}
What I can do to pass data correctly in the required format?
Integer type supports cast.
From the documentation:
class marshmallow.fields.Integer(*, strict: bool = False, **kwargs)[source]
An integer field.
Parameters
strict – If True, only integer types are valid. Otherwise, any value castable to int is valid.
kwargs – The same keyword arguments that Number receives.
So try:
class MyRequestDto(Schema):
#pre_load
def wrap_data(self, in_data, **kwargs):
return {"rooms": in_data}
rooms = Dict(String, Dict(String, Integer))
It will automatically handle the str for room_id.
If you want to keep room_id as str then you need to define a Custom Field.
Example:
class CustomField(Field):
def _serialize(self, value, attr, obj, **kwargs):
if isinstance(value, (str, int)):
return value
raise ValidationError("Value is not int or str")
def _deserialize(self, value, attr, data, **kwargs):
if isinstance(value, (str, int)):
return value
raise ValidationError("Value is not int or str")
class MyRequestDto(Schema):
#pre_load
def wrap_data(self, in_data, **kwargs):
return {"rooms": in_data}
rooms = Dict(String, Dict(String, CustomField))
I have a similar question to this previous question. However, my dictionary has a structure like the following
data_dict = {
'refresh_count': 1,
'fetch_date': '10-10-2019',
'modified_date': '',
'data': [
{'date': '10-10-2019', 'title': 'Hello1'},
{'date': '11-10-2019', 'title': 'Hello2'}
]
}
I would like to store it in JSON so that my data is still stored in one dictionary per line. Something like:
{
'refresh_count': 1,
'fetch_date': '10-10-2019',
'modified_date': '',
'data': [
{'date': '10-10-2019', 'title': 'Hello1'},
{'date': '11-10-2019', 'title': 'Hello2'}
]
}
I cannot achieve it using simply using json.dumps (or dump) or the previous solution.
json.dumps(data_dict, indent=2)
>> {
"refresh_count": 1,
"fetch_date": "10-10-2019",
"modified_date": "",
"data": [
{
"date": "10-10-2019",
"title": "Hello1"
},
{
"date": "11-10-2019",
"title": "Hello2"
}
]
}
This is quite a hack, but you can implement a custom JSON encoder that will do what you want (see Custom JSON Encoder in Python With Precomputed Literal JSON). For any object that you do not want to be indented, wrap it with the NoIndent class. The custom JSON encoder will look for this type in the default() method and return a unique string (__N__) and store unindented JSON in self._literal. Later, in the call to encode(), these unique strings are replaced with the unindented JSON.
Note that you need to choose a string format that cannot possibly appear in the encoded data to avoid replacing something unintentionally.
import json
class NoIndent:
def __init__(self, o):
self.o = o
class MyEncoder(json.JSONEncoder):
def __init__(self, *args, **kwargs):
super(MyEncoder, self).__init__(*args, **kwargs)
self._literal = []
def default(self, o):
if isinstance(o, NoIndent):
i = len(self._literal)
self._literal.append(json.dumps(o.o))
return '__%d__' % i
else:
return super(MyEncoder, self).default(o)
def encode(self, o):
s = super(MyEncoder, self).encode(o)
for i, literal in enumerate(self._literal):
s = s.replace('"__%d__"' % i, literal)
return s
data_dict = {
'refresh_count': 1,
'fetch_date': '10-10-2019',
'modified_date': '',
'data': [
NoIndent({'date': '10-10-2019', 'title': 'Hello1'}),
NoIndent({'date': '11-10-2019', 'title': 'Hello2'}),
]
}
s = json.dumps(data_dict, indent=2, cls=MyEncoder)
print(s)
Intermediate representation returned by super(MyEncoder, self).encode(o):
{
"fetch_date": "10-10-2019",
"refresh_count": 1,
"data": [
"__0__",
"__1__"
],
"modified_date": ""
}
Final output:
{
"fetch_date": "10-10-2019",
"refresh_count": 1,
"data": [
{"date": "10-10-2019", "title": "Hello1"},
{"date": "11-10-2019", "title": "Hello2"}
],
"modified_date": ""
}
I got a class with arrays of classes as properties
class classToJson():
def __init__(self, name, image, objects1, objects2):
self.name = name
self.image = image
self.boolean = True
self.objects1 = objects1
self.objects2 = objects2
def __repr__(self):
return json.dumps(self.__dict__)
object1 and object2 class looks like this:
class object1():
value1 = 1
value2 = 0
class objects2:
def __init__(self, name, value):
self.name = name
self.value = value
This is how I create my json of the class.
obj2 = [...]
parsedObject = classToJson(name, image, [object1], obj2)
file = open("{}.json".format(name),"w")
file.write("[{}]".format(parsedObject.__repr__()))
file.close()
This works if I only use name, image and boolean in the class but when I include objects1 or objects2 I get the TypeError: Object of type 'type' is not JSON serializable. Why?
The json schema I want to accomplish:
[
{
"name": "name",
"image": "image",
"boolean": true,
"objects1": [
{
"value1": 1,
"value2": 0
}
],
"objetcs2": [
{
"name": "name",
"value": "value"
}
]
}
]
Just use the built in json.dump like so
import json
with open('file.json') as f:
json.dump([{
'name': name,
'image': image,
'boolean": True,
'object1': object1,
'object2': object2
}], f)
In Flask-Restplus, I need to model an attribute value that maybe either a list of strings or a list of objects.
That is it can look like this:
{
'my_attribute': [
'value1',
'value2'
]
}
or it can look like the following:
{
'my_attribute': [
{
'name': 'value1',
'foo': 'something'
},
{
'name': 'value2',
'foo': 'something else'
}
]
}
How should I model that in Flask-Restplus’ api.model?
I've just figured this out myself. In short, create a custom field class that emits its own JSON schema. In turn the schema uses the oneOf type to specify that this is either a string or an object.
from flask_restplus import fields
element_object = api.model('Element_Object', {
'name': fields.String(),
'foo': fields.String()
})
class StringOrObjectElement(fields.Nested):
__schema_type__ = ['string','object']
def output(self, key, obj):
if isinstance(obj, str):
if key == 'name':
return obj
else:
return 'default_value'
return super().output(key, obj)
def schema(self):
schema_dict = super().schema()
schema_dict.pop('type')
nested_ref = schema_dict.pop('$ref')
schema_dict['oneOf'] = [
{
'type': 'string'
},
{
'$ref': nested_ref
}
]
return schema_dict
root_object = api.model('Root_Object', {
'my_attribute': fields.List(fields.StringOrObjectElement(element_object))
I have been looking at the answer to the following question here: How can I select deeply nested key:values from dictionary in python
But my issue isn't in finding a single key inside the deeply nested data structure, but all occurences of a particular key.
For example, like if we modify the data structure in the first example in here:
[
"stats":{
"success": true,
"payload": {
"tag": {
"slug": "python",
"name": "Python",
"postCount": 10590,
"virtuals": {
"isFollowing": false
}
},
"metadata": {
"followerCount": 18053,
"postCount": 10590,
"coverImage": {
"id": "1*O3-jbieSsxcQFkrTLp-1zw.gif",
"originalWidth": 550,
"originalHeight": 300
}
}
}
},
"stats": {
"success": true,
"payload": {
"tag": {
"slug": "python",
"name": "Python",
"postCount": 10590,
"virtuals": {
"isFollowing": false
}
},
"metadata": {
"followerCount": 18053,
"postCount": 10590,
"coverImage": {
"id": "1*O3-jbieSsxcQFkrTLp-1zw.gif",
"originalWidth": 550,
"originalHeight": 300
}
}
}
}
]
How would I get every possible occurrences of "metadata" here?
How about something recursive?
def extractVals(obj, key, resList):
if type(obj) == dict:
if key in obj:
resList.append(obj[key])
for k, v in obj.items():
extractVals(v, key, resList)
if type(obj) == list:
for l in obj:
extractVals(l, key, resList)
resultList1 = []
extractVals(dat, 'metadata', resultList1)
print(resultList1)
yields:
[{'coverImage': {'id': '1*O3-jbieSsxcQFkrTLp-1zw.gif',
'originalHeight': 300,
'originalWidth': 550},
'followerCount': 18053,
'postCount': 10590},
{'coverImage': {'id': '1*O3-jbieSsxcQFkrTLp-1zw.gif',
'originalHeight': 300,
'originalWidth': 550},
'followerCount': 18053,
'postCount': 10590}]
I also had to modify your dataset slightly above to be a valid Python structure. true -> True, false -> False, and removed the keys from the top level list.
You can use a custon class like this one:
class DeepDict:
def __init__(self, data):
self.data = data
#classmethod
def _deep_find(cls, data, key, root, response):
if root:
root += "."
if isinstance(data, list):
for i, item in enumerate(data):
cls._deep_find(item, key, root + str(i), response)
elif isinstance(data, dict):
if key in data:
response.append(root + key)
for data_key, value in data.items():
cls._deep_find(value, key, root + data_key, response)
return response
def deep_find(self, key):
""" Returns all ocurrences of `key` with a dottedpath leading to each.
Use `deepget` to retrieve the values for a given ocurrence, or
`get_all` to iterate over the values for each occurrence of the key.
"""
return self._deep_find(self.data, key, root="", response=[])
#classmethod
def _deep_get(cls, data, path):
if not path:
return data
index = path.pop(0)
if index.isdigit():
index = int(index)
return cls._deep_get(data[index], path)
def deep_get(self, path):
if isinstance(path, str):
path = path.split(".")
return self._deep_get(self.data, path)
def get_all(self, key):
for path in self.deep_find(key):
yield self.deep_get(path)
def __getitem__(self, key):
if key.isdigit():
key = int(key)
return self.data[key]
(Note that although I named it "DeepDict" it is actually a generic JSON container that will work with both lists and dicts as outer elements. BTW, the JSON fragment in your question is broken - both "stats": keys should be wrapped in an extra { })
So, these three custom methods can either find you the precise "path" to each occurrence of a key, or, you can use the get_all method to simply get the contents of how many keys with that name are in the structure as an iterator.
With the above class, after fixing your data I did:
data = DeepDict(<data structure above (fixed)>)
list(data.get_all("metadata"))
and got as output:
[{'coverImage': {'id': '1*O3-jbieSsxcQFkrTLp-1zw.gif',
'originalHeight': 300,
'originalWidth': 550},
'followerCount': 18053,
'postCount': 10590},
{'coverImage': {'id': '1*O3-jbieSsxcQFkrTLp-1zw.gif',
'originalHeight': 300,
'originalWidth': 550},
'followerCount': 18053,
'postCount': 10590}]