Encoding for JSON Arguments

In Dropbox API v2, content-upload and content-download endpoints take their JSON arguments in the Dropbox-API-Arg HTTP header or in the arg URL parameter.

If you're using an official Dropbox SDK, you do not need to manually encode your parameters; the SDK will do this for you.

If you're not using an official Dropbox SDK, you should make sure to apply the proper encoding. Some libraries will automatically do this for you.

If you use the arg URL parameter, generate a JSON string, encode it as UTF-8, then use the application/x-www-form-urlencoded rules to encode the value.

If you use the Dropbox-API-Arg header, you need to make it "HTTP header safe". This means using JSON-style "\uXXXX" escape codes for the character 0x7F and all non-ASCII characters.

We based this determination on the original HTTP/1.1 specification (RFC 2616) and as well as the newer HTTP/1.1 specification (RFC 7230). The former suggests that all you need to escape is 0x7F. The later recommends escaping all non-ASCII characters.

Generating HTTP-header-safe JSON

Here are examples of making HTTP-header-safe JSON in a few different languages. As an example, each of these takes the input:

{"field": "some_üñîcødé_and_" + chr(0x7F)}

and produce the properly encoded string:

'{"field": "some_\u00fc\u00f1\u00eec\u00f8d\u00e9_and_\u007f"}'

C# (Json.NET, JsonConvert):

using System.Collections.Generic;
using Newtonsoft.Json;

var serializerSettings = new JsonSerializerSettings();
serializerSettings.StringEscapeHandling = StringEscapeHandling.EscapeNonAscii;

var args = new Dictionary<string, string> { { "field", "some_üñîcødé_and_\x7f"} };

var encodedArgs = JsonConvert.SerializeObject(args, serializerSettings);
encodedArgs = encodedArgs.Replace("\x7F", "\\u007f");

C# (Json.NET, JsonTextWriter):

using System.IO;
using System.Text;
using Newtonsoft.Json;

var stringBuilder = new StringBuilder();
var jsonWriter = new JsonTextWriter(new StringWriter(stringBuilder));
jsonWriter.StringEscapeHandling = StringEscapeHandling.EscapeNonAscii;

jsonWriter.WriteStartObject();
jsonWriter.WritePropertyName("field");
jsonWriter.WriteValue("some_üñîcødé_and_\x7f");
jsonWriter.WriteEndObject();

var encodedArgs = stringBuilder.ToString().Replace("\x7F", "\\u007f");

Java (Jackson):

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;


JsonFactory jsonFactory = new JsonFactory();
JsonGenerator jsonGenerator = jsonFactory.createGenerator(System.out);
jsonGenerator.setHighestNonEscapedChar(0x7E);

jsonGenerator.setCharacterEscapes(new CharacterEscapes() {
    @Override
    public int[] getEscapeCodesForAscii() {
        int[] esc = CharacterEscapes.standardAsciiEscapesForJSON();
        esc[0x7F] = CharacterEscapes.ESCAPE_STANDARD;
        return esc;
    }

    @Override
    public SerializableString getEscapeSequence(int i) {
        throw new NotImplementedException();
    }
});

jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("field", "some_üñîcødé_and_\u007F");
jsonGenerator.writeEndObject();
jsonGenerator.close();

JavaScript:

var charsToEncode = /[\u007f-\uffff]/g;

// This function is simple and has OK performance compared to more
// complicated ones: http://jsperf.com/json-escape-unicode/4
function http_header_safe_json(v) {
  return JSON.stringify(v).replace(charsToEncode,
    function(c) {
      return '\\u'+('000'+c.charCodeAt(0).toString(16)).slice(-4);
    }
  );
}

var args = {"field": "some_üñîcødé_and_" + String.fromCharCode(0x7F)}

var encoded_args = http_header_safe_json(args)

Objective-C:

+ (NSString *)utf8StringWithData:(NSData *)jsonData {
    return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}

+ (NSString *)asciiEscapeWithString:(NSString *)string {
    NSMutableString *result = [[NSMutableString alloc] init];
    for (NSUInteger i = 0; i < string.length; i++) {
        NSString *substring = [string substringWithRange:NSMakeRange(i, 1)];
        if ([substring canBeConvertedToEncoding:NSASCIIStringEncoding] &&
            substring != [NSString stringWithFormat:@"%C", 0x7F]) {
            [result appendString:substring];
        } else {
            [result appendFormat:@"\\u%04x", [string characterAtIndex:i]];
        }
    }

    return result;
}

NSDictionary* args = @{
                       @"field": [NSString stringWithFormat:@"some_üñîcødé_and_%C", 0x7F],
                       };

NSData *jsonData = [NSJSONSerialization dataWithJSONObject:args options:NSJSONWritingSortedKeys error:nil];
NSString *encodedArgs = [[self class] asciiEscapeWithString:[[self class] utf8StringWithData:jsonData]];

Python:

import json

args = {"field": "some_üñîcødé_and_" + chr(0x7F)}

# dumps() takes an optional 'ascii_only' parameter, which defaults to True
# This will correctly encode 0x7F and above.
encoded_args = json.dumps(args)

PHP:

$args = array("field" => "some_üñîcødé_and_" . chr(0x7F));

$encodedArgs = json_encode($args);
$encodedArgs = str_replace(chr(0x7F), "\\u007f", $encodedArgs);

Swift:

func utf8Decode(_ data: Data) -> String {
    return NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
}

func asciiEscape(_ s: String) -> String {
    var out: String = ""

    for char in s.unicodeScalars {
        var esc = "\(char)"
        if !char.isASCII {
            esc = NSString(format:"\\u%04x", char.value) as String
        } else {
            if (char == "\u{7F}") {
                esc = "\\u007f"
            } else {
                esc = "\(char)"
            }
        }
        out += esc

    }
    return out
}

var args : [String:String] = ["field": "some_üñîcødé_and_\u{7F}"]

let jsonData = try JSONSerialization.data(withJSONObject: args)
let encodedArgs = asciiEscape(utf8Decode(jsonData))