ts-proto 2.10.1 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.markdown CHANGED
@@ -414,6 +414,9 @@ Generated code will be placed in the Gradle build directory.
414
414
 
415
415
  The default behavior is `keys_json`, i.e. both will be camel cased, and `json_name` will be used if set.
416
416
 
417
+ - With `--ts_proto_opt=protoJsonFormat=true` (default), the `toJSON` and `fromJSON` methods follow the [protoJSON format](https://protobuf.dev/programming-guides/json). This means `toJSON` will output standard lowerCamelCase keys (or custom `json_name` if set). `fromJSON` will accept both the lowerCamelCase key (or `json_name`) AND the original proto field name.
418
+ With `--ts_proto_opt=protoJsonFormat=false`, strict proto3 compliance is disabled. The JSON keys will simply follow the `snakeToCamel` option, and `fromJSON` will only accept that specific format.
419
+
417
420
  - With `--ts_proto_opt=outputEncodeMethods=false`, the `Message.encode` and `Message.decode` methods for working with protobuf-encoded/binary data will not be output.
418
421
 
419
422
  This is useful if you want "only types".
package/build/src/main.js CHANGED
@@ -1964,6 +1964,9 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1964
1964
  };
1965
1965
  const noDefaultValue = !options.initializeFieldsAsUndefined &&
1966
1966
  (0, types_1.isOptionalProperty)(field, messageDesc.options, options, currentFile.isProto3Syntax);
1967
+ // if the protoJsonFormat option is enabled and the field name is different from the json name, we need to
1968
+ // accept both formats in the fromJSON method.
1969
+ let protoJsonComparison = (0, ts_poet_1.code) ``;
1967
1970
  // and then use the snippet to handle repeated fields if necessary
1968
1971
  if (canonicalFromJson[fullTypeName]?.[fieldName]) {
1969
1972
  chunks.push((0, ts_poet_1.code) `${fieldName}: ${canonicalFromJson[fullTypeName][fieldName]("object")},`);
@@ -1975,23 +1978,45 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
1975
1978
  const i = convertFromObjectKey(ctx, messageDesc, field, "key");
1976
1979
  if ((0, types_1.shouldGenerateJSMapType)(ctx, messageDesc, field)) {
1977
1980
  const fallback = noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : "new Map()";
1981
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
1982
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
1983
+ protoJsonComparison = (0, ts_poet_1.code) `
1984
+ : ${ctx.utils.isObject}(${protoJsonProperty})
1985
+ ? (${ctx.utils.globalThis}.Object.entries(${protoJsonProperty}) as [string, any][]).reduce((acc: ${fieldType}, [key, value]: [string, any]) => {
1986
+ acc.set(${i}, ${readSnippet("value")});
1987
+ return acc;
1988
+ }, new Map())
1989
+ `;
1990
+ }
1978
1991
  chunks.push((0, ts_poet_1.code) `
1979
1992
  ${fieldKey}: ${ctx.utils.isObject}(${jsonProperty})
1980
1993
  ? (${ctx.utils.globalThis}.Object.entries(${jsonProperty}) as [string, any][]).reduce((acc: ${fieldType}, [key, value]: [string, any]) => {
1981
1994
  acc.set(${i}, ${readSnippet("value")});
1982
1995
  return acc;
1983
1996
  }, new Map())
1997
+ ${protoJsonComparison}
1984
1998
  : ${fallback},
1985
1999
  `);
1986
2000
  }
1987
2001
  else {
1988
2002
  const fallback = noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : "{}";
2003
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2004
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2005
+ protoJsonComparison = (0, ts_poet_1.code) `
2006
+ : ${ctx.utils.isObject}(${protoJsonProperty})
2007
+ ? (${ctx.utils.globalThis}.Object.entries(${protoJsonProperty}) as [string, any][]).reduce((acc: ${fieldType}, [key, value]: [string, any]) => {
2008
+ acc[${i}] = ${readSnippet("value")};
2009
+ return acc;
2010
+ }, {})
2011
+ `;
2012
+ }
1989
2013
  chunks.push((0, ts_poet_1.code) `
1990
2014
  ${fieldKey}: ${ctx.utils.isObject}(${jsonProperty})
1991
2015
  ? (${ctx.utils.globalThis}.Object.entries(${jsonProperty}) as [string, any][]).reduce((acc: ${fieldType}, [key, value]: [string, any]) => {
1992
2016
  acc[${i}] = ${readSnippet("value")};
1993
2017
  return acc;
1994
2018
  }, {})
2019
+ ${protoJsonComparison}
1995
2020
  : ${fallback},
1996
2021
  `);
1997
2022
  }
@@ -2000,12 +2025,22 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
2000
2025
  const fallback = noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : "[]";
2001
2026
  const needMap = readSnippet("e").toString() !== (0, ts_poet_1.code) `e`.toString();
2002
2027
  if (!needMap) {
2003
- chunks.push((0, ts_poet_1.code) `${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonPropertyOptional}) ? [...${jsonProperty}] : [],`);
2028
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2029
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2030
+ const protoJsonPropertyOptional = (0, utils_1.getPropertyAccessor)("object", field.name, true);
2031
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.globalThis}.Array.isArray(${protoJsonPropertyOptional}) ? [...${protoJsonProperty}]`;
2032
+ }
2033
+ chunks.push((0, ts_poet_1.code) `${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonPropertyOptional}) ? [...${jsonProperty}] ${protoJsonComparison} : [],`);
2004
2034
  }
2005
2035
  else {
2006
2036
  // Explicit `any` type required to make TS with noImplicitAny happy. `object` is also `any` here.
2037
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2038
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2039
+ const protoJsonPropertyOptional = (0, utils_1.getPropertyAccessor)("object", field.name, true);
2040
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.globalThis}.Array.isArray(${protoJsonPropertyOptional}) ? ${protoJsonProperty}.map((e: any) => ${readSnippet("e")})`;
2041
+ }
2007
2042
  chunks.push((0, ts_poet_1.code) `
2008
- ${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonPropertyOptional}) ? ${jsonProperty}.map((e: any) => ${readSnippet("e")}): ${fallback},
2043
+ ${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonPropertyOptional}) ? ${jsonProperty}.map((e: any) => ${readSnippet("e")}) ${protoJsonComparison} : ${fallback},
2009
2044
  `);
2010
2045
  }
2011
2046
  }
@@ -2022,33 +2057,59 @@ function generateFromJson(ctx, fullName, fullTypeName, messageDesc) {
2022
2057
  const ternaryIf = (0, ts_poet_1.code) `${ctx.utils.isSet}(${jsonProperty})`;
2023
2058
  const ternaryThen = (0, ts_poet_1.code) `{ $case: '${fieldName}', ${valueName}: ${readSnippet(`${jsonProperty}`)}`;
2024
2059
  chunks.push((0, ts_poet_1.code) `${ternaryIf} ? ${ternaryThen}} : `);
2060
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2061
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2062
+ const ternaryOriginalIf = (0, ts_poet_1.code) `${ctx.utils.isSet}(${protoJsonProperty})`;
2063
+ const ternaryOriginalThen = (0, ts_poet_1.code) `{ $case: '${fieldName}', ${valueName}: ${readSnippet(`${protoJsonProperty}`)}`;
2064
+ chunks.push((0, ts_poet_1.code) `${ternaryOriginalIf} ? ${ternaryOriginalThen}} : `);
2065
+ }
2025
2066
  if (field === lastCase) {
2026
2067
  chunks.push((0, ts_poet_1.code) `${(0, utils_1.nullOrUndefined)(options)},`);
2027
2068
  }
2028
2069
  }
2029
2070
  else if ((0, types_1.isAnyValueType)(field)) {
2071
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2072
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2073
+ const protoJsonPropertyOptional = (0, utils_1.getPropertyAccessor)("object", field.name, true);
2074
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.isSet}(${protoJsonPropertyOptional}) ? ${readSnippet(`${protoJsonProperty}`)}`;
2075
+ }
2030
2076
  chunks.push((0, ts_poet_1.code) `${fieldKey}: ${ctx.utils.isSet}(${jsonPropertyOptional})
2031
2077
  ? ${readSnippet(`${jsonProperty}`)}
2078
+ ${protoJsonComparison}
2032
2079
  : ${(0, utils_1.nullOrUndefined)(options)},
2033
2080
  `);
2034
2081
  }
2035
2082
  else if ((0, types_1.isStructType)(field)) {
2083
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2084
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2085
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.isObject}(${protoJsonProperty}) ? ${readSnippet(`${protoJsonProperty}`)}`;
2086
+ }
2036
2087
  chunks.push((0, ts_poet_1.code) `${fieldKey}: ${ctx.utils.isObject}(${jsonProperty})
2037
2088
  ? ${readSnippet(`${jsonProperty}`)}
2089
+ ${protoJsonComparison}
2038
2090
  : ${(0, utils_1.nullOrUndefined)(options)},`);
2039
2091
  }
2040
2092
  else if ((0, types_1.isListValueType)(field)) {
2093
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2094
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2095
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.globalThis}.Array.isArray(${protoJsonProperty}) ? ${readSnippet(`${protoJsonProperty}`)}`;
2096
+ }
2041
2097
  chunks.push((0, ts_poet_1.code) `
2042
2098
  ${fieldKey}: ${ctx.utils.globalThis}.Array.isArray(${jsonProperty})
2043
2099
  ? ${readSnippet(`${jsonProperty}`)}
2100
+ ${protoJsonComparison}
2044
2101
  : ${(0, utils_1.nullOrUndefined)(options)},
2045
2102
  `);
2046
2103
  }
2047
2104
  else {
2048
2105
  const fallback = (0, types_1.isWithinOneOf)(field) || noDefaultValue ? (0, utils_1.nullOrUndefined)(options) : (0, types_1.defaultValue)(ctx, field);
2106
+ if (options.protoJsonFormat && field.name !== field.jsonName) {
2107
+ const protoJsonProperty = (0, utils_1.getPropertyAccessor)("object", field.name);
2108
+ protoJsonComparison = (0, ts_poet_1.code) ` : ${ctx.utils.isSet}(${protoJsonProperty}) ? ${readSnippet(`${protoJsonProperty}`)}`;
2109
+ }
2049
2110
  chunks.push((0, ts_poet_1.code) `
2050
- ${fieldKey}: ${ctx.utils.isSet}(${jsonProperty})
2051
- ? ${readSnippet(`${jsonProperty}`)}
2111
+ ${fieldKey}: ${ctx.utils.isSet}(${jsonProperty}) ? ${readSnippet(`${jsonProperty}`)}
2112
+ ${protoJsonComparison}
2052
2113
  : ${fallback},
2053
2114
  `);
2054
2115
  }
@@ -45,6 +45,7 @@ export declare enum OutputSchemaOption {
45
45
  export type Options = {
46
46
  context: boolean;
47
47
  snakeToCamel: Array<"json" | "keys">;
48
+ protoJsonFormat: boolean;
48
49
  forceLong: LongOption;
49
50
  useJsTypeOverride: boolean;
50
51
  globalThisPolyfill: boolean;
@@ -60,6 +60,7 @@ function defaultOptions() {
60
60
  return {
61
61
  context: false,
62
62
  snakeToCamel: ["json", "keys"],
63
+ protoJsonFormat: true,
63
64
  emitDefaultValues: [],
64
65
  globalThisPolyfill: false,
65
66
  forceLong: LongOption.NUMBER,
@@ -204,6 +205,11 @@ function optionsFromParameter(parameter) {
204
205
  else if (typeof options.snakeToCamel === "string") {
205
206
  options.snakeToCamel = options.snakeToCamel.split("_");
206
207
  }
208
+ if (options.protoJsonFormat && !options.snakeToCamel.includes("json")) {
209
+ // protoJSONFormat implies snakeToCamel=json, Message field names must be mapped to lowerCamelCase and become JSON object keys.
210
+ // If the json_name field option is specified, the specified value will be used as the key instead.
211
+ options.snakeToCamel.push("json");
212
+ }
207
213
  if (options.emitDefaultValues === "json-methods") {
208
214
  options.emitDefaultValues = ["json-methods"];
209
215
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-proto",
3
- "version": "2.10.1",
3
+ "version": "2.11.0",
4
4
  "description": "",
5
5
  "main": "build/src/plugin.js",
6
6
  "repository": "github:stephenh/ts-proto",