Skip to content

Commit

Permalink
fix: support mutliple options in snakeToCamel flag (#429)
Browse files Browse the repository at this point in the history
* fix: support mutliple options in snakeToCamel flag
Changed `snakeToCamel: boolean` to `snakeToCamel: boolean | string`.
So that `snakeToCamel` would look like `snakeToCamel: Array<"keys" | "json">`.

So user can set true / false and then map those to true --> ["keys"] and false --> [] and then keys,json => ["keys", "json"]
resolves #423

* docs: update ts_proto_opt usage in readme
  • Loading branch information
ajaykarthikr committed Dec 13, 2021
1 parent 89baa9f commit cff6674
Show file tree
Hide file tree
Showing 6 changed files with 36 additions and 30 deletions.
4 changes: 1 addition & 3 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,7 @@ Generated code will be placed in the Gradle build directory.

- With `--ts_proto_opt=lowerCaseServiceMethods=true`, the method names of service methods will be lowered/camel-case, i.e. `service.findFoo` instead of `service.FindFoo`.

- With `--ts_proto_opt=snakeToCamel=false`, fields will be kept snake case.

Defaults to `true`.
- With `--ts_proto_opt=snakeToCamel=false`, fields will be kept snake case. `snakeToCamel` can also be set as string with `--ts_proto_opt=snakeToCamel=keys,json`. `keys` will keep field names as camelCase and `json` will keep json field names as camelCase. Empty string will keep field names as snake_case.

- With `--ts_proto_opt=outputEncodeMethods=false`, the `Message.encode` and `Message.decode` methods for working with protobuf-encoded/binary data will not be output.

Expand Down
4 changes: 2 additions & 2 deletions integration/simple-snake/import_dir/thing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ export const ImportedThing = {
fromJSON(object: any): ImportedThing {
const message = { ...baseImportedThing } as ImportedThing;
message.created_at =
object.createdAt !== undefined && object.createdAt !== null ? fromJsonTimestamp(object.createdAt) : undefined;
object.created_at !== undefined && object.created_at !== null ? fromJsonTimestamp(object.created_at) : undefined;
return message;
},

toJSON(message: ImportedThing): unknown {
const obj: any = {};
message.created_at !== undefined && (obj.createdAt = message.created_at.toISOString());
message.created_at !== undefined && (obj.created_at = message.created_at.toISOString());
return obj;
},

Expand Down
40 changes: 20 additions & 20 deletions integration/simple-snake/simple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,13 +344,13 @@ export const Simple = {
message.name = object.name !== undefined && object.name !== null ? String(object.name) : '';
message.age = object.age !== undefined && object.age !== null ? Number(object.age) : 0;
message.created_at =
object.createdAt !== undefined && object.createdAt !== null ? fromJsonTimestamp(object.createdAt) : undefined;
object.created_at !== undefined && object.created_at !== null ? fromJsonTimestamp(object.created_at) : undefined;
message.child = object.child !== undefined && object.child !== null ? Child.fromJSON(object.child) : undefined;
message.state = object.state !== undefined && object.state !== null ? stateEnumFromJSON(object.state) : 0;
message.grand_children = (object.grandChildren ?? []).map((e: any) => Child.fromJSON(e));
message.grand_children = (object.grand_children ?? []).map((e: any) => Child.fromJSON(e));
message.coins = (object.coins ?? []).map((e: any) => Number(e));
message.snacks = (object.snacks ?? []).map((e: any) => String(e));
message.old_states = (object.oldStates ?? []).map((e: any) => stateEnumFromJSON(e));
message.old_states = (object.old_states ?? []).map((e: any) => stateEnumFromJSON(e));
message.thing =
object.thing !== undefined && object.thing !== null ? ImportedThing.fromJSON(object.thing) : undefined;
return message;
Expand All @@ -360,13 +360,13 @@ export const Simple = {
const obj: any = {};
message.name !== undefined && (obj.name = message.name);
message.age !== undefined && (obj.age = message.age);
message.created_at !== undefined && (obj.createdAt = message.created_at.toISOString());
message.created_at !== undefined && (obj.created_at = message.created_at.toISOString());
message.child !== undefined && (obj.child = message.child ? Child.toJSON(message.child) : undefined);
message.state !== undefined && (obj.state = stateEnumToJSON(message.state));
if (message.grand_children) {
obj.grandChildren = message.grand_children.map((e) => (e ? Child.toJSON(e) : undefined));
obj.grand_children = message.grand_children.map((e) => (e ? Child.toJSON(e) : undefined));
} else {
obj.grandChildren = [];
obj.grand_children = [];
}
if (message.coins) {
obj.coins = message.coins.map((e) => e);
Expand All @@ -379,9 +379,9 @@ export const Simple = {
obj.snacks = [];
}
if (message.old_states) {
obj.oldStates = message.old_states.map((e) => stateEnumToJSON(e));
obj.old_states = message.old_states.map((e) => stateEnumToJSON(e));
} else {
obj.oldStates = [];
obj.old_states = [];
}
message.thing !== undefined && (obj.thing = message.thing ? ImportedThing.toJSON(message.thing) : undefined);
return obj;
Expand Down Expand Up @@ -894,21 +894,21 @@ export const SimpleWithMap = {

fromJSON(object: any): SimpleWithMap {
const message = { ...baseSimpleWithMap } as SimpleWithMap;
message.entitiesById = Object.entries(object.entitiesById ?? {}).reduce<{ [key: number]: Entity }>(
message.entitiesById = Object.entries(object.entities_by_id ?? {}).reduce<{ [key: number]: Entity }>(
(acc, [key, value]) => {
acc[Number(key)] = Entity.fromJSON(value);
return acc;
},
{}
);
message.nameLookup = Object.entries(object.nameLookup ?? {}).reduce<{ [key: string]: string }>(
message.nameLookup = Object.entries(object.name_lookup ?? {}).reduce<{ [key: string]: string }>(
(acc, [key, value]) => {
acc[key] = String(value);
return acc;
},
{}
);
message.intLookup = Object.entries(object.intLookup ?? {}).reduce<{ [key: number]: number }>(
message.intLookup = Object.entries(object.int_lookup ?? {}).reduce<{ [key: number]: number }>(
(acc, [key, value]) => {
acc[Number(key)] = Number(value);
return acc;
Expand All @@ -920,22 +920,22 @@ export const SimpleWithMap = {

toJSON(message: SimpleWithMap): unknown {
const obj: any = {};
obj.entitiesById = {};
obj.entities_by_id = {};
if (message.entitiesById) {
Object.entries(message.entitiesById).forEach(([k, v]) => {
obj.entitiesById[k] = Entity.toJSON(v);
obj.entities_by_id[k] = Entity.toJSON(v);
});
}
obj.nameLookup = {};
obj.name_lookup = {};
if (message.nameLookup) {
Object.entries(message.nameLookup).forEach(([k, v]) => {
obj.nameLookup[k] = v;
obj.name_lookup[k] = v;
});
}
obj.intLookup = {};
obj.int_lookup = {};
if (message.intLookup) {
Object.entries(message.intLookup).forEach(([k, v]) => {
obj.intLookup[k] = v;
obj.int_lookup[k] = v;
});
}
return obj;
Expand Down Expand Up @@ -1180,7 +1180,7 @@ export const SimpleWithSnakeCaseMap = {

fromJSON(object: any): SimpleWithSnakeCaseMap {
const message = { ...baseSimpleWithSnakeCaseMap } as SimpleWithSnakeCaseMap;
message.entities_by_id = Object.entries(object.entitiesById ?? {}).reduce<{ [key: number]: Entity }>(
message.entities_by_id = Object.entries(object.entities_by_id ?? {}).reduce<{ [key: number]: Entity }>(
(acc, [key, value]) => {
acc[Number(key)] = Entity.fromJSON(value);
return acc;
Expand All @@ -1192,10 +1192,10 @@ export const SimpleWithSnakeCaseMap = {

toJSON(message: SimpleWithSnakeCaseMap): unknown {
const obj: any = {};
obj.entitiesById = {};
obj.entities_by_id = {};
if (message.entities_by_id) {
Object.entries(message.entities_by_id).forEach(([k, v]) => {
obj.entitiesById[k] = Entity.toJSON(v);
obj.entities_by_id[k] = Entity.toJSON(v);
});
}
return obj;
Expand Down
8 changes: 7 additions & 1 deletion src/case.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Options } from './options';

export function maybeSnakeToCamel(s: string, options: Pick<Options, 'snakeToCamel'>): string {
if (options.snakeToCamel && s.includes('_')) {
const fromStringSnakeToCamel = typeof options.snakeToCamel === 'string' && options.snakeToCamel.includes('keys');
const fromBooleanSnakeToCamel = options.snakeToCamel === true;
if ((fromBooleanSnakeToCamel || fromStringSnakeToCamel) && s.includes('_')) {
return s
.split('_')
.map((word, i) => {
Expand All @@ -18,6 +20,10 @@ export function maybeSnakeToCamel(s: string, options: Pick<Options, 'snakeToCame
}
}

export function stringToSnakeCase(s: string) {
return s.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
}

export function camelToSnake(s: string): string {
return s
.replace(/[\w]([A-Z])/g, function (m) {
Expand Down
2 changes: 1 addition & 1 deletion src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export enum ServiceOption {

export type Options = {
context: boolean;
snakeToCamel: boolean;
snakeToCamel: boolean | string;
forceLong: LongOption;
useOptionals: boolean;
useDate: DateOption;
Expand Down
8 changes: 5 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import ReadStream = NodeJS.ReadStream;
import { SourceDescription } from './sourceInfo';
import { Options, ServiceOption } from './options';
import { camelCase, maybeSnakeToCamel } from './case';
import { camelCase, stringToSnakeCase } from './case';

export function protoFilesToGenerate(request: CodeGeneratorRequest): FileDescriptorProto[] {
return request.protoFile.filter((f) => request.fileToGenerate.includes(f.name));
Expand Down Expand Up @@ -177,10 +177,12 @@ export class FormattedMethodDescriptor implements MethodDescriptorProto {
export function determineFieldJsonName(field: FieldDescriptorProto, options: Options): string {
// By default jsonName is camelCased by the protocol compilier unless the user has
// set a "json_name" option on this field.
if (field.jsonName.length > 0) {
const fromStringSnakeToCamel = typeof options.snakeToCamel === 'string' && options.snakeToCamel.includes('json');
const fromBooleanSnakeToCamel = options.snakeToCamel === true;
if (fromBooleanSnakeToCamel || fromStringSnakeToCamel) {
return field.jsonName;
}
return maybeSnakeToCamel(field.name, options);
return stringToSnakeCase(field.name);
}

export function impProto(options: Options, module: string, type: string): Import {
Expand Down

0 comments on commit cff6674

Please sign in to comment.