diff --git a/src/core/ext/client_channel/client_channel.c b/src/core/ext/client_channel/client_channel.c
index 139912e4bac122cd054832ddd698f540078190cc..a92a220c745e15c0f0ec8f286f10e7086ba2f64a 100644
--- a/src/core/ext/client_channel/client_channel.c
+++ b/src/core/ext/client_channel/client_channel.c
@@ -90,7 +90,7 @@ static void *method_parameters_create_from_json(const grpc_json *json) {
   gpr_timespec timeout = {0, 0, GPR_TIMESPAN};
   for (grpc_json *field = json->child; field != NULL; field = field->next) {
     if (field->key == NULL) continue;
-    if (strcmp(field->key, "wait_for_ready") == 0) {
+    if (strcmp(field->key, "waitForReady") == 0) {
       if (wait_for_ready != WAIT_FOR_READY_UNSET) return NULL;  // Duplicate.
       if (field->type != GRPC_JSON_TRUE && field->type != GRPC_JSON_FALSE) {
         return NULL;
@@ -99,26 +99,39 @@ static void *method_parameters_create_from_json(const grpc_json *json) {
                                                      : WAIT_FOR_READY_FALSE;
     } else if (strcmp(field->key, "timeout") == 0) {
       if (timeout.tv_sec > 0 || timeout.tv_nsec > 0) return NULL;  // Duplicate.
-      if (field->type != GRPC_JSON_OBJECT) return NULL;
-      if (field->child == NULL) return NULL;
-      for (grpc_json *subfield = field->child; subfield != NULL;
-           subfield = subfield->next) {
-        if (subfield->key == NULL) return NULL;
-        if (strcmp(subfield->key, "seconds") == 0) {
-          if (timeout.tv_sec > 0) return NULL;  // Duplicate.
-          if (subfield->type != GRPC_JSON_NUMBER) return NULL;
-          timeout.tv_sec = gpr_parse_nonnegative_number(subfield->value);
-          if (timeout.tv_sec == -1) return NULL;
-        } else if (strcmp(subfield->key, "nanos") == 0) {
-          if (timeout.tv_nsec > 0) return NULL;  // Duplicate.
-          if (subfield->type != GRPC_JSON_NUMBER) return NULL;
-          timeout.tv_nsec = gpr_parse_nonnegative_number(subfield->value);
-          if (timeout.tv_nsec == -1) return NULL;
-        } else {
-          // Unknown key.
+      if (field->type != GRPC_JSON_STRING) return NULL;
+      size_t len = strlen(field->value);
+      if (field->value[len - 1] != 's') return NULL;
+      char* buf = gpr_strdup(field->value);
+      buf[len - 1] = '\0';  // Remove trailing 's'.
+      char* decimal_point = strchr(buf, '.');
+      if (decimal_point != NULL) {
+        *decimal_point = '\0';
+        timeout.tv_nsec = gpr_parse_nonnegative_int(decimal_point + 1);
+        if (timeout.tv_nsec == -1) {
+          gpr_free(buf);
           return NULL;
         }
+        // There should always be exactly 3, 6, or 9 fractional digits.
+        int multiplier = 1;
+        switch (strlen(decimal_point + 1)) {
+          case 9:
+            break;
+          case 6:
+            multiplier *= 1000;
+            break;
+          case 3:
+            multiplier *= 1000000;
+            break;
+          default:  // Unsupported number of digits.
+            gpr_free(buf);
+            return NULL;
+        }
+        timeout.tv_nsec *= multiplier;
       }
+      timeout.tv_sec = gpr_parse_nonnegative_int(buf);
+      if (timeout.tv_sec == -1) return NULL;
+      gpr_free(buf);
     }
   }
   method_parameters *value = gpr_malloc(sizeof(method_parameters));
diff --git a/src/core/lib/channel/message_size_filter.c b/src/core/lib/channel/message_size_filter.c
index 507ea26c0580e11d1d55de6bdae97d1c5ece7357..28ad587c0e48b57f47b32f4a15d5132c8c10cca3 100644
--- a/src/core/lib/channel/message_size_filter.c
+++ b/src/core/lib/channel/message_size_filter.c
@@ -65,15 +65,15 @@ static void* message_size_limits_create_from_json(const grpc_json* json) {
   int max_response_message_bytes = -1;
   for (grpc_json* field = json->child; field != NULL; field = field->next) {
     if (field->key == NULL) continue;
-    if (strcmp(field->key, "max_request_message_bytes") == 0) {
+    if (strcmp(field->key, "maxRequestMessageBytes") == 0) {
       if (max_request_message_bytes >= 0) return NULL;  // Duplicate.
-      if (field->type != GRPC_JSON_NUMBER) return NULL;
-      max_request_message_bytes = gpr_parse_nonnegative_number(field->value);
+      if (field->type != GRPC_JSON_STRING) return NULL;
+      max_request_message_bytes = gpr_parse_nonnegative_int(field->value);
       if (max_request_message_bytes == -1) return NULL;
-    } else if (strcmp(field->key, "max_response_message_bytes") == 0) {
+    } else if (strcmp(field->key, "maxResponseMessageBytes") == 0) {
       if (max_response_message_bytes >= 0) return NULL;  // Duplicate.
-      if (field->type != GRPC_JSON_NUMBER) return NULL;
-      max_response_message_bytes = gpr_parse_nonnegative_number(field->value);
+      if (field->type != GRPC_JSON_STRING) return NULL;
+      max_response_message_bytes = gpr_parse_nonnegative_int(field->value);
       if (max_response_message_bytes == -1) return NULL;
     }
   }
diff --git a/src/core/lib/support/string.c b/src/core/lib/support/string.c
index aa58dd15e377045e9e83034e4954f93b79ab72f9..f10a30f0fd5f7dd14d86e52011cf976ecb017a94 100644
--- a/src/core/lib/support/string.c
+++ b/src/core/lib/support/string.c
@@ -191,7 +191,7 @@ int int64_ttoa(int64_t value, char *string) {
   return i;
 }
 
-int gpr_parse_nonnegative_number(const char *value) {
+int gpr_parse_nonnegative_int(const char *value) {
   char *end;
   long result = strtol(value, &end, 0);
   if (*end != '\0' || result < 0 || result > INT_MAX) return -1;
diff --git a/src/core/lib/support/string.h b/src/core/lib/support/string.h
index a691ebb2a65f2536a76d11650585208cd876e011..e933e2eb468c6b9818ceaf0d1b9901bd5cbe4f37 100644
--- a/src/core/lib/support/string.h
+++ b/src/core/lib/support/string.h
@@ -78,7 +78,7 @@ where long is 32bit is size.*/
 int int64_ttoa(int64_t value, char *output);
 
 // Parses a non-negative number from a value string.  Returns -1 on error.
-int gpr_parse_nonnegative_number(const char *value);
+int gpr_parse_nonnegative_int(const char *value);
 
 /* Reverse a run of bytes */
 void gpr_reverse_bytes(char *str, int len);
diff --git a/src/core/lib/transport/service_config.c b/src/core/lib/transport/service_config.c
index a8494cbe8b5d799ae99841e9ecfd15e6a3fefc29..2e2b59e3f760c7b2fdd5103653bb945734248cb7 100644
--- a/src/core/lib/transport/service_config.c
+++ b/src/core/lib/transport/service_config.c
@@ -46,8 +46,8 @@
 // JSON form, which will look like this:
 //
 // {
-//   "lb_policy_name": "string",  // optional
-//   "method_config": [  // array of one or more method_config objects
+//   "loadBalancingPolicy": "string",  // optional
+//   "methodConfig": [  // array of one or more method_config objects
 //     {
 //       "name": [  // array of one or more name objects
 //         {
@@ -55,15 +55,13 @@
 //           "method": "string",  // optional
 //         }
 //       ],
-//       // remaining fields are optional
-//       "wait_for_ready": bool,
-//       "timeout": {
-//         // one or both of these fields may be specified
-//         "seconds": number,
-//         "nanos": number,
-//       },
-//       "max_request_message_bytes": number,
-//       "max_response_message_bytes": number
+//       // remaining fields are optional.
+//       // see https://developers.google.com/protocol-buffers/docs/proto3#json
+//       // for format details.
+//       "waitForReady": bool,
+//       "timeout": "duration_string",
+//       "maxRequestMessageBytes": "int64_string",
+//       "maxResponseMessageBytes": "int64_string",
 //     }
 //   ]
 // }
@@ -100,7 +98,7 @@ const char* grpc_service_config_get_lb_policy_name(
   const char* lb_policy_name = NULL;
   for (grpc_json* field = json->child; field != NULL; field = field->next) {
     if (field->key == NULL) return NULL;
-    if (strcmp(field->key, "lb_policy_name") == 0) {
+    if (strcmp(field->key, "loadBalancingPolicy") == 0) {
       if (lb_policy_name != NULL) return NULL;  // Duplicate.
       if (field->type != GRPC_JSON_STRING) return NULL;
       lb_policy_name = field->value;
@@ -194,7 +192,7 @@ grpc_mdstr_hash_table* grpc_service_config_create_method_config_table(
   grpc_mdstr_hash_table_entry* entries = NULL;
   for (grpc_json* field = json->child; field != NULL; field = field->next) {
     if (field->key == NULL) return NULL;
-    if (strcmp(field->key, "method_config") == 0) {
+    if (strcmp(field->key, "methodConfig") == 0) {
       if (entries != NULL) return NULL;  // Duplicate.
       if (field->type != GRPC_JSON_ARRAY) return NULL;
       // Find number of entries.
diff --git a/test/core/end2end/connection_refused_test.c b/test/core/end2end/connection_refused_test.c
index c14d72f49e2d372076c547632d8505238aea565c..728d6dca5900bf4fb098e3f401731479416b83db 100644
--- a/test/core/end2end/connection_refused_test.c
+++ b/test/core/end2end/connection_refused_test.c
@@ -81,11 +81,11 @@ static void run_test(bool wait_for_ready, bool use_service_config) {
     arg.key = GRPC_ARG_SERVICE_CONFIG;
     arg.value.string =
         "{\n"
-        "  \"method_config\": [ {\n"
+        "  \"methodConfig\": [ {\n"
         "    \"name\": [\n"
         "      { \"service\": \"service\", \"method\": \"method\" }\n"
         "    ],\n"
-        "    \"wait_for_ready\": true\n"
+        "    \"waitForReady\": true\n"
         "  } ]\n"
         "}";
     args = grpc_channel_args_copy_and_add(args, &arg, 1);
diff --git a/test/core/end2end/tests/cancel_after_accept.c b/test/core/end2end/tests/cancel_after_accept.c
index 28d24aa30610c9d7ca74f9fc2cfb5d4a7fd0e4d7..e582c59f2d37de76a1db72b79804befff7ca7a2f 100644
--- a/test/core/end2end/tests/cancel_after_accept.c
+++ b/test/core/end2end/tests/cancel_after_accept.c
@@ -139,11 +139,11 @@ static void test_cancel_after_accept(grpc_end2end_test_config config,
     arg.key = GRPC_ARG_SERVICE_CONFIG;
     arg.value.string =
         "{\n"
-        "  \"method_config\": [ {\n"
+        "  \"methodConfig\": [ {\n"
         "    \"name\": [\n"
         "      { \"service\": \"service\", \"method\": \"method\" }\n"
         "    ],\n"
-        "    \"timeout\": { \"seconds\": 5 }\n"
+        "    \"timeout\": \"5s\"\n"
         "  } ]\n"
         "}";
     args = grpc_channel_args_copy_and_add(args, &arg, 1);
diff --git a/test/core/end2end/tests/max_message_length.c b/test/core/end2end/tests/max_message_length.c
index 90dae50b75f87b96802b5b0a524061c01455edbe..c222d9dfae7053ae58b3fa32b5edcca614ab6fd7 100644
--- a/test/core/end2end/tests/max_message_length.c
+++ b/test/core/end2end/tests/max_message_length.c
@@ -143,11 +143,11 @@ static void test_max_message_length_on_request(grpc_end2end_test_config config,
     arg.key = GRPC_ARG_SERVICE_CONFIG;
     arg.value.string =
         "{\n"
-        "  \"method_config\": [ {\n"
+        "  \"methodConfig\": [ {\n"
         "    \"name\": [\n"
         "      { \"service\": \"service\", \"method\": \"method\" }\n"
         "    ],\n"
-        "    \"max_request_message_bytes\": 5\n"
+        "    \"maxRequestMessageBytes\": \"5\"\n"
         "  } ]\n"
         "}";
     client_args = grpc_channel_args_copy_and_add(NULL, &arg, 1);
@@ -317,11 +317,11 @@ static void test_max_message_length_on_response(grpc_end2end_test_config config,
     arg.key = GRPC_ARG_SERVICE_CONFIG;
     arg.value.string =
         "{\n"
-        "  \"method_config\": [ {\n"
+        "  \"methodConfig\": [ {\n"
         "    \"name\": [\n"
         "      { \"service\": \"service\", \"method\": \"method\" }\n"
         "    ],\n"
-        "    \"max_response_message_bytes\": 5\n"
+        "    \"maxResponseMessageBytes\": \"5\"\n"
         "  } ]\n"
         "}";
     client_args = grpc_channel_args_copy_and_add(NULL, &arg, 1);