Seeing an interesting issue, not sure this is to do with parser or the way it suppose to parse. Any help is appreciated
import groovy.json.JsonSlurper
def dMatch = '''[{"match":{"keyId":"A-102161-application"}},{"match":{"keyId":"A-102162-application"}},{"match":{"keyId":"A-102163-application"}},{"match":{"keyId":"A-102164-application"}},{"match":{"keyId":"A-102165-application"}}]'''
println "T1:: List: " + dMatch
def parser = new JsonSlurper()
def exclude = parser.parseText(dMatch)
println "T2:: Obj: " + exclude.toString()
println "----------------------------------------------------"
Output :
T1:: List: [{"match":{"keyId":"A-102161-application"}},
{"match":{"keyId":"A-102162-application"}},
{"match":{"keyId":"A-102163-application"}},
{"match":{"keyId":"A-102164-application"}},
{"match":{"keyId":"A-102165-application"}}]
T2:: Obj: *[[match:[keyId:A-102161-application]],
[match:[keyId:A-102162-application]],
[match:[keyId:A-102163-application]],
[match:[keyId:A-102164-application]],
[match:[keyId:A-102165-application]]]*
The parsed object supposed to be same as the string but all the values were converted as array list of map.
Any idea why this is generating object like this ? When this is sent to camunda it complains
org.camunda.bpm.engine.ProcessEngineException: Cannot serialize object in variable 'exclude': groovy.json.internal.LazyMap
Use JsonSlurperClassic() - it produces standard HashMap that is serializable.
And if you want to convert object back to json use Json output.toJson(obj)
I have 2 JSON files and I want to merge those 2 and create one JSON message using groovy. Based on the "type" value I'm going to merge those two JSON files.
If the given "type" of JSON objects of JSON message 1 does not exist
in the JSON message2, the relevant JSON object should be contained in the output JSON message.
All the JSON objects from JSON message2 should be contained in the
output JSON message
Expected sample formats is shown below
Input JSON message1
{"message":[{"name":"HelloFile","type": "input"},{"name":"SecondFile","type": "error"}]
Input JSON message2
[{"name":"NewFile","type": "input"},{"name":"MyFile","type": "output"}]
Expected JSON
{"message":[{"name":"NewFile","type": "input"},{"name":"MyFile","type": "output"},{"name":"SecondFile","type": "error"}]}
I used the below groovy code.
JsonBuilder jsonBuilder = new JsonBuilder(JSON1)
jsonBuilder.content.message= JSON2
def updatedBody = jsonBuilder.toString()
From the above code, I got the below message.
{"message":[{"name":"NewFile","type": "input"},{"name":"MyFile","type": "output"}]}
Any help sorting this would be much appreciated.
Try using JsonSlurper:
import groovy.json.*
def json1 = '{"message":[{"name":"HelloFile","type": "input"},{"name":"SecondFile","type": "error"}]}'
def json2 = '[{"name":"NewFile","type": "input"},{"name":"MyFile","type": "output"}]'
def slurper = new JsonSlurper()
def json1Obj = slurper.parseText(json1)
def json2Obj = slurper.parseText(json2)
json1Obj.message+=json2Obj
println JsonOutput.toJson(json1Obj)
This prints:
{"message":[{"name":"HelloFile","type":"input"},{"name":"SecondFile","type":"error"},{"name":"NewFile","type":"input"},{"name":"MyFile","type":"output"}]}
I am trying to correct the Incoming JSON as I have a JSON to XML converter. I wish to replace the leading number in a field etc 1Doc1 to S_Doc1 etc. Also I Need to replace the invalid XML element names from JSON such as Slash etc. Here is my Code but it is not working:
def list = new JsonSlurper().parseText( payload )
list.each {
def oldStr = "" + it
def newStr = oldStr.replaceFirst("^[^a-zA-Z]+", "S_")
payload = payload.replaceFirst(oldStr, newStr)
}
return payload
I get the Input as is. Could anyone advise how to do this in Groovy. For example if my Input is:
{
"1Document1":
{"Record":{"Header"...….
The Output should be
{
"S_Document1":
{"Record":{"Header"......
You can use eachWithIndex and update the element in the list using key instead of trying to manipulate the input string:
import groovy.json.JsonSlurper
String json = '[{"1Document1": {"Record":{"Header": "xx"}}}, {"2Document1": {"Record":{"Header": "zz"}}}]'
def list = new JsonSlurper().parseText( json )
list.eachWithIndex {v, k ->
def newStr = (""+v).replaceFirst("^[^a-zA-Z]+", "S_")
list[k] = newStr
}
println list
I am trying to write a Django Rest Framework API handler that can receive a file as well as a JSON payload. I've set the MultiPartParser as the handler parser.
However, it seems I cannot do both. If I send the payload with the file as a multi part request, the JSON payload is available in a mangled manner in the request.data (first text part until the first colon as the key, the rest is the data). I can send the parameters in standard form parameters just fine - but the rest of my API accepts JSON payloads and I wanted to be consistent. The request.body cannot be read as it raises *** RawPostDataException: You cannot access body after reading from request's data stream
For example, a file and this payload in the request body:
{"title":"Document Title", "description":"Doc Description"}
Becomes:
<QueryDict: {u'fileUpload': [<InMemoryUploadedFile: 20150504_115355.jpg (image/jpeg)>, <InMemoryUploadedFile: Front end lead.doc (application/msword)>], u'{%22title%22': [u'"Document Title", "description":"Doc Description"}']}>
Is there a way to do this? Can I eat my cake, keep it and not gain any weight?
Edit:
It was suggested that this might be a copy of Django REST Framework upload image: "The submitted data was not a file". It is not. The upload and request is done in multipart, and keep in mind the file and upload of it is fine. I can even complete the request with standard form variables. But I want to see if I can get a JSON payload in there instead.
For someone who needs to upload a file and send some data, there is no straight fwd way you can get it to work. There is an open issue in json api specs for this. One possibility i have seen is to use multipart/related as shown here, but i think its very hard to implement it in drf.
Finally what i had implemented was to send the request as formdata. You would send each file as file and all other data as text.
Now for sending the data as text you can have a single key called data and send the whole json as string in value.
Models.py
class Posts(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
caption = models.TextField(max_length=1000)
media = models.ImageField(blank=True, default="", upload_to="posts/")
tags = models.ManyToManyField('Tags', related_name='posts')
serializers.py -> no special changes needed, not showing my serializer here as its too lengthy because of the writable ManyToMany Field implimentation.
views.py
class PostsViewset(viewsets.ModelViewSet):
serializer_class = PostsSerializer
parser_classes = (MultipartJsonParser, parsers.JSONParser)
queryset = Posts.objects.all()
lookup_field = 'id'
You will need custom parser as shown below for parsing json.
utils.py
from django.http import QueryDict
import json
from rest_framework import parsers
class MultipartJsonParser(parsers.MultiPartParser):
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
data = {}
# find the data field and parse it
data = json.loads(result.data["data"])
qdict = QueryDict('', mutable=True)
qdict.update(data)
return parsers.DataAndFiles(qdict, result.files)
The request example in postman
EDIT:
see this extended answer if you want to send each data as key value pair
I know this is an old thread, but I just came across this. I had to use MultiPartParser in order to get my file and extra data to come across together. Here's what my code looks like:
# views.py
class FileUploadView(views.APIView):
parser_classes = (MultiPartParser,)
def put(self, request, filename, format=None):
file_obj = request.data['file']
ftype = request.data['ftype']
caption = request.data['caption']
# ...
# do some stuff with uploaded file
# ...
return Response(status=204)
My AngularJS code using ng-file-upload is:
file.upload = Upload.upload({
url: "/api/picture/upload/" + file.name,
data: {
file: file,
ftype: 'final',
caption: 'This is an image caption'
}
});
I send JSON and an image to create/update a product object. Below is a create APIView that works for me.
Serializer
class ProductCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = [
"id",
"product_name",
"product_description",
"product_price",
]
def create(self,validated_data):
return Product.objects.create(**validated_data)
View
from rest_framework import generics,status
from rest_framework.parsers import FormParser,MultiPartParser
class ProductCreateAPIView(generics.CreateAPIView):
queryset = Product.objects.all()
serializer_class = ProductCreateSerializer
permission_classes = [IsAdminOrIsSelf,]
parser_classes = (MultiPartParser,FormParser,)
def perform_create(self,serializer,format=None):
owner = self.request.user
if self.request.data.get('image') is not None:
product_image = self.request.data.get('image')
serializer.save(owner=owner,product_image=product_image)
else:
serializer.save(owner=owner)
Example test:
def test_product_creation_with_image(self):
url = reverse('products_create_api')
self.client.login(username='testaccount',password='testaccount')
data = {
"product_name" : "Potatoes",
"product_description" : "Amazing Potatoes",
"image" : open("local-filename.jpg","rb")
}
response = self.client.post(url,data)
self.assertEqual(response.status_code,status.HTTP_201_CREATED)
#Nithin solution works but essentially it means you are sending JSON as strings and hence not using the actual application/json inside the multipart segments.
What we want is to make the backend accept data in the below format
------WebKitFormBoundaryrga771iuUYap8BB2
Content-Disposition: form-data; name="file"; filename="1x1_noexif.jpeg"
Content-Type: image/jpeg
------WebKitFormBoundaryrga771iuUYap8BB2
Content-Disposition: form-data; name="myjson"; filename="blob"
Content-Type: application/json
{"hello":"world"}
------WebKitFormBoundaryrga771iuUYap8BB2
Content-Disposition: form-data; name="isDownscaled"; filename="blob"
Content-Type: application/json
false
------WebKitFormBoundaryrga771iuUYap8BB2--
MultiPartParser works with the above format but will treat those jsons as files. So we simply unmarshal those jsons by putting them to data.
parsers.py
from rest_framework import parsers
class MultiPartJSONParser(parsers.MultiPartParser):
def parse(self, stream, *args, **kwargs):
data = super().parse(stream, *args, **kwargs)
# Any 'File' found having application/json as type will be moved to data
mutable_data = data.data.copy()
unmarshaled_blob_names = []
json_parser = parsers.JSONParser()
for name, blob in data.files.items():
if blob.content_type == 'application/json' and name not in data.data:
mutable_data[name] = json_parser.parse(blob)
unmarshaled_blob_names.append(name)
for name in unmarshaled_blob_names:
del data.files[name]
data.data = mutable_data
return data
settings.py
REST_FRAMEWORK = {
..
'DEFAULT_PARSER_CLASSES': [
..
'myproject.parsers.MultiPartJSONParser',
],
}
This should work now.
The final bit is testing. Since the test client that ships with Django and REST doesn't support multipart JSON, we work around that by wrapping any JSON data.
import io
import json
def JsonBlob(obj):
stringified = json.dumps(obj)
blob = io.StringIO(stringified)
blob.content_type = 'application/json'
return blob
def test_simple(client, png_3x3):
response = client.post(f'http://localhost/files/', {
'file': png_3x3,
'metadata': JsonBlob({'lens': 'Sigma 35mm'}),
}, format='multipart')
assert response.status_code == 200
If you're getting an error along the lines of Incorrect type. Expected pk value, received list., with #nithin's solution, it's because Django's QueryDict is getting in the way - it's specifically structured to use a list for each entry in the dictionary, and thus:
{ "list": [1, 2] }
when parsed by MultipartJsonParser yields
{ 'list': [[1, 2]] }
which trips up your serializer.
Here is an alternative which handles this case, specifically expecting the _data key for your JSON:
from rest_framework import parsers
import json
class MultiPartJSONParser(parsers.MultiPartParser):
def parse(self, stream, *args, **kwargs):
data = super().parse(stream, *args, **kwargs)
json_data_field = data.data.get('_data')
if json_data_field is not None:
parsed = json.loads(json_data_field)
mutable_data = {}
for key, value in parsed.items():
mutable_data[key] = value
mutable_files = {}
for key, value in data.files.items():
if key != '_data':
mutable_files[key] = value
return parsers.DataAndFiles(mutable_data, mutable_files)
json_data_file = data.files.get('_data')
if json_data_file:
parsed = parsers.JSONParser().parse(json_data_file)
mutable_data = {}
for key, value in parsed.items():
mutable_data[key] = value
mutable_files = {}
for key, value in data.files.items():
mutable_files[key] = value
return parsers.DataAndFiles(mutable_data, mutable_files)
return data
It is very simple to use a multipart post and a regular view, if this is an option.
You send the json as a field and files as files, then process in one view.
Here is a simple python client and a Django server:
Client - sending multiple files and an arbitrary json-encoded object:
import json
import requests
payload = {
"field1": 1,
"manifest": "special cakes",
"nested": {"arbitrary":1, "object":[1,2,3]},
"hello": "word" }
filenames = ["file1","file2"]
request_files = {}
url="example.com/upload"
for filename in filenames:
request_files[filename] = open(filename, 'rb')
r = requests.post(url, data={'json':json.dumps(payload)}, files=request_files)
Server - consuming the json and saving the files:
#csrf_exempt
def upload(request):
if request.method == 'POST':
data = json.loads(request.POST['json'])
try:
manifest = data['manifest']
#process the json data
except KeyError:
HttpResponseServerError("Malformed data!")
dir = os.path.join(settings.MEDIA_ROOT, "uploads")
os.makedirs(dir, exist_ok=True)
for file in request.FILES:
path = os.path.join(dir,file)
if not os.path.exists(path):
save_uploaded_file(path, request.FILES[file])
else:
return HttpResponseNotFound()
return HttpResponse("Got json data")
def save_uploaded_file(path,f):
with open(path, 'wb+') as destination:
for chunk in f.chunks():
destination.write(chunk)
I'd just like to add to #Pithikos's answer by modifying the parser to accept lists as well, in line with how DRF parses lists in serializers in utils/html#parse_html_list
class MultiPartJSONParser(parsers.MultiPartParser):
def parse(self, stream, *args, **kwargs):
data = super().parse(stream, *args, **kwargs)
# Any 'File' found having application/json as type will be moved to data
mutable_data = data.data.copy()
unmarshaled_blob_names = []
json_parser = parsers.JSONParser()
for name, blob in data.files.items():
if blob.content_type == 'application/json' and name not in data.data:
parsed = json_parser.parse(blob)
if isinstance(parsed, list):
# need to break it out into [0], [1] etc
for idx, item in enumerate(parsed):
mutable_data[name+f"[{str(idx)}]"] = item
else:
mutable_data[name] = parsed
unmarshaled_blob_names.append(name)
for name in unmarshaled_blob_names:
del data.files[name]
data.data = mutable_data
return data
The following code worked for me.
from django.core.files.uploadedfile import SimpleUploadedFile
import requests
from typing import Dict
with open(file_path, 'rb') as f:
file = SimpleUploadedFile('Your-Name', f.read())
data: Dict[str,str]
files: Dict[str,SimpleUploadedFile] = {'model_field_name': file}
requests.put(url, headers=headers, data=data, files=files)
requests.post(url, headers=headers, data=data, files=files)
'model_field_name' is the name of the FileField or ImageField in your Django model. You can pass other data as name or location as usual by using data parameter.
Hope this helps.
This work for me:
class FileUpload(APIView):
parser_classes = [MultiPartParser]
authentication_classes = [JWTAuthentication]
def post(self, request, filename, format=None):
file = request.data['file']
data = json.loads(request.POST['form'])
#.... just do.....
.
.
.
frontend part: example with fetch (vue frontend)
let data = await new FormData(); // creates a new FormData object
data.append("file", this.files); // add your file to form data
data.append('form',JSON.stringify(body)) //add your json
fetch(`https://endpoint/FileUpload/${body.nombre}`, {
method: 'POST',
body: data,
headers: {Authorization: `Bearer ${accessToken}`}
})
I hope this helps.
I have this code:
"response=3&responsetext=Duplicate transaction REFID:3154223053&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=auth&response_code=300"
I tried converting it into json format using this code:
def converted = "{\"" + resp.data.toString()
.replaceAll('=','\":\"')
.replaceAll('&','\",\"') + "\"}"
it returns the valid json format though I want to get a specific value from that string I tried doing:
println converted.responsetext.toString()
it has an error saying
No such property: responsetext for class: java.lang.String
You can convert request parameters to Map and if required to json string as below:
String str = "response=3&responsetext=Duplicate transaction REFID:3154223053&authcode=&transactionid=&avsresponse=&cvvresponse=&orderid=&type=auth&response_code=300"
def map = str.tokenize(/&/).collectEntries {
def entity = it.tokenize(/=/)
[ entity[0], entity[1] ]
}
assert map.responsetext == "Duplicate transaction REFID:3154223053"
// Json
println new groovy.json.JsonBuilder( map ).toPrettyString()
If you want to go ahead from what you have right now instead, then below implementation should be sufficient:
def items = new groovy.json.JsonSlurper().parseText( converted )
assert items.responsetext == "Duplicate transaction REFID:3154223053"