Repository: stephenh/ts-proto (ts)
Issue: Snake to Camel unexpected result
I have a proto file like this
```proto
syntax = "proto3";

package sso.v2;

message Client {
    string clientI_d = 1;
    string client_secret = 2;
}

message LoginRequest {
    string redirectUR_i = 12;
}
```

**after**:
```bash
protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_opt=nestJs=true --ts_proto_opt=env=node --ts_proto_opt=addGrpcMetadata=true --ts_proto_opt=useExactTypes=false --ts_proto_opt=useOptionals=messages --ts_proto_opt=forceLong=string --ts_proto_out=. *.proto
```
**produce**:
```typescript
/* eslint-disable */
import { util, configure } from "protobufjs/minimal";
import * as Long from "long";

export const protobufPackage = "sso.v2";

export interface Client {
  clientiD: string;
  clientSecret: string;
}

export interface LoginRequest {
  redirecturI: string;
}

export const SSO_V2_PACKAGE_NAME = "sso.v2";

// If you get a compile-error about 'Constructor<Long> and ... have no overlap',
// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'.
if (util.Long !== Long) {
  util.Long = Long as any;
  configure();
}

```

the expected result should be `clientID` instead of `clientiD` and `redirectURI` instead of `redirecturI`

Pull request description: fix: Have snakeToCamel leave existing mixed case.
Fixes #478

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bc79d608..74a91c3d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## [1.102.1](https://github.com/stephenh/ts-proto/compare/v1.102.0...v1.102.1) (2022-01-19)
+
+
+### Bug Fixes
+
+* Pin ts-proto-descriptors to 1.3.1. ([#481](https://github.com/stephenh/ts-proto/issues/481)) ([6f362bf](https://github.com/stephenh/ts-proto/commit/6f362bfd3517a6bcb440d65e7ac63cd2b0bcc293)), closes [#480](https://github.com/stephenh/ts-proto/issues/480)
+
 # [1.102.0](https://github.com/stephenh/ts-proto/compare/v1.101.0...v1.102.0) (2022-01-18)
 
 
diff --git a/package.json b/package.json
index 12149247..7d091b55 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "ts-proto",
-  "version": "1.102.0",
+  "version": "1.102.1",
   "description": "",
   "main": "build/plugin.js",
   "repository": "github:stephenh/ts-proto",
diff --git a/src/case.ts b/src/case.ts
index 5e57b5fa..4cd77012 100644
--- a/src/case.ts
+++ b/src/case.ts
@@ -2,12 +2,13 @@ import { Options } from './options';
 
 export function maybeSnakeToCamel(s: string, options: Pick<Options, 'snakeToCamel'>): string {
   if (options.snakeToCamel.includes('keys') && s.includes('_')) {
+    const hasLowerCase = !!s.match(/[a-z]/);
     return s
       .split('_')
       .map((word, i) => {
         if (i === 0) {
           // if first symbol is "_" then skip it
-          return word ? word[0] + word.substring(1).toLowerCase() : '';
+          return word ? word[0] + (hasLowerCase ? word.substring(1) : word.substring(1).toLowerCase()) : '';
         } else {
           return capitalize(word.toLowerCase());
         }


Test patch:
diff --git a/tests/case-test.ts b/tests/case-test.ts
index 15c2536c..1e5a5576 100644
--- a/tests/case-test.ts
+++ b/tests/case-test.ts
@@ -16,6 +16,10 @@ describe('case', () => {
     expect(maybeSnakeToCamel('FOO_BAR', keys)).toEqual('FooBar');
   });
 
+  it('leaves existing mixed cases', () => {
+    expect(maybeSnakeToCamel('clientI_d', keys)).toEqual('clientID');
+  });
+
   it('leaves the first character as it was', () => {
     expect(maybeSnakeToCamel('Foo_Bar', keys)).toEqual('FooBar');
   });