Different behavior when sending Canvas RESTful API request with JSON body versus x-www-form-urlencoded body

maguire
Community Champion

I recently tried passing a JSON encoded body in a request to create a section - rather than passing a /x-www-form-urlencoded body. The results were really surprising - rather than creating a section named "DumbleTest" it created a section with the name "II2202 Collaboration Course 2018-08-08 ". The output below shows the requests and replies with the token replaced by "xxx" (I have manually highlighted the key differences):

./create_section_test.py -v 5694 "DumbleTest"
ARGV : ['-v', '5694', 'DumbleTest']
VERBOSE : True
REMAINING : ['5694', 'DumbleTest']
Testing : False
'Wed Aug 8 12:10:06 2018'
url: https://kth.test.instructure.com/api/v1/courses/5694/sections
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): kth.test.instructure.com:443
send: b'POST /api/v1/courses/5694/sections HTTP/1.1\r\nHost: kth.test.instructure.com\r\nUser-Agent: python-requests/2.19.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nAuthorization: Bearer xxx\r\nContent-Length: 38\r\nContent-Type: application/json\r\n\r\n'
send: b'{"course_section[name]": "DumbleTest"}'
reply: 'HTTP/1.1 200 OK\r\n'
DEBUG:urllib3.connectionpool:https://kth.test.instructure.com:443 "POST /api/v1/courses/5694/sections HTTP/1.1" 200 163
header: Cache-Control header: Content-Encoding header: Content-Type header: Date header: ETag header: P3P header: Server header: Set-Cookie header: Set-Cookie header: Set-Cookie header: Status header: Strict-Transport-Security header: Vary header: X-Canvas-Meta header: X-Canvas-User-Id header: X-Content-Type-Options header: X-Frame-Options header: X-Rate-Limit-Remaining header: X-Request-Context-Id header: X-Request-Cost header: X-Request-Processor header: X-Robots-Tag header: X-Runtime header: X-Session-Id header: X-UA-Compatible header: X-XSS-Protection header: Content-Length header: Connection ('result of creating section: {"id":11124,"course_id":5694,"name":"II2202 '
'Collaboration Course '
'2018-08-08","start_at":null,"end_at":null,"restrict_enrollments_to_section_dates":null,"nonxlist_course_id":null,"sis_section_id":null,"sis_course_id":null,"integration_id":null}')
----------------------------------------------------------------------
./create_section_test.py -v 5694 "DumbleTest"
ARGV : ['-v', '5694', 'DumbleTest']
VERBOSE : True
REMAINING : ['5694', 'DumbleTest']
Testing : False
'Wed Aug 8 12:12:25 2018'
url: https://kth.test.instructure.com/api/v1/courses/5694/sections
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): kth.test.instructure.com:443
send: b'POST /api/v1/courses/5694/sections HTTP/1.1\r\nHost: kth.test.instructure.com\r\nUser-Agent: python-requests/2.19.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nAuthorization: Bearer xxx\r\nContent-Length: 35\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\n'
send: b'course_section%5Bname%5D=DumbleTest'
reply: 'HTTP/1.1 200 OK\r\n'
DEBUG:urllib3.connectionpool:https://kth.test.instructure.com:443 "POST /api/v1/courses/5694/sections HTTP/1.1" 200 143
header: Cache-Control header: Content-Encoding header: Content-Type header: Date header: ETag header: P3P header: Server header: Set-Cookie header: Set-Cookie header: Set-Cookie header: Status header: Strict-Transport-Security header: Vary header: X-Canvas-Meta header: X-Canvas-User-Id header: X-Content-Type-Options header: X-Frame-Options header: X-Rate-Limit-Remaining header: X-Request-Context-Id header: X-Request-Cost header: X-Request-Processor header: X-Robots-Tag header: X-Runtime header: X-Session-Id header: X-UA-Compatible header: X-XSS-Protection header: Content-Length header: Connection ('result of creating section: '
'{"id":11125,"course_id":5694,"name":"DumbleTest","start_at":null,"end_at":null,"restrict_enrollments_to_section_dates":null,"nonxlist_course_id":null,"sis_section_id":null,"sis_course_id":null,"integration_id":null}')

However, if rather than sending '{"course_section[name]": "DumbleTest"}' one sends '{"course_section": {"name": "DumbleTest2"}}' it works as expected:

./create_section_test.py -v 5694 "DumbleTest2"
ARGV : ['-v', '5694', 'DumbleTest2']
VERBOSE : True
REMAINING : ['5694', 'DumbleTest2']
Testing : False
'Wed Aug 8 12:38:43 2018'
url: https://kth.test.instructure.com/api/v1/courses/5694/sections
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): kth.test.instructure.com:443
send: b'POST /api/v1/courses/5694/sections HTTP/1.1\r\nHost: kth.test.instructure.com\r\nUser-Agent: python-requests/2.19.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nAuthorization: Bearer xxx\r\nContent-Length: 43\r\nContent-Type: application/json\r\n\r\n'
send: b'{"course_section": {"name": "DumbleTest2"}}'
reply: 'HTTP/1.1 200 OK\r\n'
DEBUG:urllib3.connectionpool:https://kth.test.instructure.com:443 "POST /api/v1/courses/5694/sections HTTP/1.1" 200 144
header: Cache-Control header: Content-Encoding header: Content-Type header: Date header: ETag header: P3P header: Server header: Set-Cookie header: Set-Cookie header: Set-Cookie header: Status header: Strict-Transport-Security header: Vary header: X-Canvas-Meta header: X-Canvas-User-Id header: X-Content-Type-Options header: X-Frame-Options header: X-Rate-Limit-Remaining header: X-Request-Context-Id header: X-Request-Cost header: X-Request-Processor header: X-Robots-Tag header: X-Runtime header: X-Session-Id header: X-UA-Compatible header: X-XSS-Protection header: Content-Length header: Connection ('result of creating section: '
'{"id":11126,"course_id":5694,"name":"DumbleTest2","start_at":null,"end_at":null,"restrict_enrollments_to_section_dates":null,"nonxlist_course_id":null,"sis_section_id":null,"sis_course_id":null,"integration_id":null}')

I can understand the logic in the difference in functionality, but do not understand why if the JSON is not considered to have the proper form why it would would make up a name for the section based on today's date.

The code can be found at Difference between JSON arguments: Chip sandbox