From 30956b64f0fbd687dce501a08869392b1fcedce8 Mon Sep 17 00:00:00 2001 From: berryware <231598+berryware@users.noreply.github.com> Date: Sat, 16 May 2026 16:16:23 -0400 Subject: [PATCH] - Check STRING() for null before getting its value. If null set value to null else to the parsed string. ____________________________________________________________________ Signed-off-by: berryware <231598+berryware@users.noreply.github.com> --- .../json/JsonCodecParseMethodGenerator.java | 40 ++++++++++--------- .../com/hedera/pbj/runtime/JsonTools.java | 4 +- .../pbj/integration/test/JsonCodecTest.java | 17 ++++++++ 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/pbj-core/pbj-compiler/src/main/java/com/hedera/pbj/compiler/impl/generators/json/JsonCodecParseMethodGenerator.java b/pbj-core/pbj-compiler/src/main/java/com/hedera/pbj/compiler/impl/generators/json/JsonCodecParseMethodGenerator.java index 19949e74b..58f71603c 100644 --- a/pbj-core/pbj-compiler/src/main/java/com/hedera/pbj/compiler/impl/generators/json/JsonCodecParseMethodGenerator.java +++ b/pbj-core/pbj-compiler/src/main/java/com/hedera/pbj/compiler/impl/generators/json/JsonCodecParseMethodGenerator.java @@ -33,8 +33,7 @@ static String generateUnsetOneOfConstants(final List fields) { return """ /** Constant for an unset oneof for $fieldName */ public static final $className<$enum> $unsetFieldName = new $className<>($enum.UNSET,null); - """ - .replace("$className", field.className()) + """.replace("$className", field.className()) .replace("$enum", field.getEnumClassRef()) .replace("$fieldName", field.name()) .replace("$unsetFieldName", Common.camelToUpperSnake(field.name()) + "_UNSET") @@ -94,8 +93,7 @@ static String generateParseObjectMethod(final String modelClassName, final List< throw new ParseException(ex); } } - """ - .replace("$modelClassName", modelClassName) + """.replace("$modelClassName", modelClassName) .replace( "$fieldDefs", fields.stream() @@ -166,9 +164,12 @@ private static void generateFieldCaseStatement( case FLOAT -> sb.append("parseFloat(v)"); case DOUBLE -> sb.append("parseDouble(v)"); case STRING -> - sb.append("unescape(checkSize(\"$fieldName\", v.STRING().getText(), $maxSize))" - .replace("$maxSize", field.maxSize() >= 0 ? String.valueOf(field.maxSize()) : "maxSize") - .replace("$fieldName", field.name())); + sb.append( + "unescape(checkSize(\"$fieldName\", v.STRING()==null?null:v.STRING().getText(), $maxSize))" + .replace( + "$maxSize", + field.maxSize() >= 0 ? String.valueOf(field.maxSize()) : "maxSize") + .replace("$fieldName", field.name())); case BOOL -> sb.append("parseBoolean(v)"); // maxSize * 2 - because Base64. The *2 math isn't precise, but it's good enough for our purposes. @@ -190,9 +191,12 @@ private static void generateFieldCaseStatement( case "FloatValue" -> sb.append("parseFloat($valueGetter)"); case "DoubleValue" -> sb.append("parseDouble($valueGetter)"); case "StringValue" -> - sb.append("unescape(checkSize(\"$fieldName\", $valueGetter.STRING().getText(), $maxSize))" - .replace("$maxSize", field.maxSize() >= 0 ? String.valueOf(field.maxSize()) : "maxSize") - .replace("$fieldName", field.name())); + sb.append( + "unescape(checkSize(\"$fieldName\", $valueGetter.STRING()==null?null:$valueGetter.STRING().getText(), $maxSize))" + .replace( + "$maxSize", + field.maxSize() >= 0 ? String.valueOf(field.maxSize()) : "maxSize") + .replace("$fieldName", field.name())); case "BoolValue" -> sb.append("parseBoolean($valueGetter)"); // maxSize * 2 - because Base64. The *2 math isn't precise, but it's good enough for our purposes: @@ -214,15 +218,12 @@ private static void generateFieldCaseStatement( generateFieldCaseStatement(keySB, mapField.keyField(), "mapKV"); generateFieldCaseStatement(valueSB, mapField.valueField(), "mapKV.value()"); - sb.append( - """ + sb.append(""" $valueGetter.getChild(JSONParser.ObjContext.class, 0).pair().stream() .collect(Collectors.toMap( mapKV -> $mapEntryKey, new UncheckedThrowingFunction<>(mapKV -> $mapEntryValue) - ))""" - .replace("$mapEntryKey", keySB.toString()) - .replace("$mapEntryValue", valueSB.toString())); + ))""".replace("$mapEntryKey", keySB.toString()).replace("$mapEntryValue", valueSB.toString())); } else { switch (field.type()) { case MESSAGE -> @@ -237,9 +238,12 @@ private static void generateFieldCaseStatement( case FLOAT -> sb.append("parseFloat($valueGetter)"); case DOUBLE -> sb.append("parseDouble($valueGetter)"); case STRING -> - sb.append("unescape(checkSize(\"$fieldName\", $valueGetter.STRING().getText(), $maxSize))" - .replace("$maxSize", field.maxSize() >= 0 ? String.valueOf(field.maxSize()) : "maxSize") - .replace("$fieldName", field.name())); + sb.append( + "unescape(checkSize(\"$fieldName\", $valueGetter.STRING()==null?null:$valueGetter.STRING().getText(), $maxSize))" + .replace( + "$maxSize", + field.maxSize() >= 0 ? String.valueOf(field.maxSize()) : "maxSize") + .replace("$fieldName", field.name())); case BOOL -> sb.append("parseBoolean($valueGetter)"); // maxSize * 2 - because Base64. The *2 math isn't precise, but it's good enough for our purposes: diff --git a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/JsonTools.java b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/JsonTools.java index 943ae8cab..196124d3b 100644 --- a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/JsonTools.java +++ b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/JsonTools.java @@ -144,9 +144,9 @@ public static List checkSize(@NonNull final String fieldName, @NonNull fi * @return the string itself * @throws UncheckedParseException if the string size exceeds `maxSize` */ - public static String checkSize(@NonNull final String fieldName, @NonNull final String string, final int maxSize) + public static String checkSize(@NonNull final String fieldName, final String string, final int maxSize) throws UncheckedParseException { - if (string.length() > maxSize) { + if (string != null && string.length() > maxSize) { throw new UncheckedParseException( new ParseException(fieldName + " size " + string.length() + " is greater than max " + maxSize)); } diff --git a/pbj-integration-tests/src/test/java/com/hedera/pbj/integration/test/JsonCodecTest.java b/pbj-integration-tests/src/test/java/com/hedera/pbj/integration/test/JsonCodecTest.java index 38becaa82..b9929e4e2 100644 --- a/pbj-integration-tests/src/test/java/com/hedera/pbj/integration/test/JsonCodecTest.java +++ b/pbj-integration-tests/src/test/java/com/hedera/pbj/integration/test/JsonCodecTest.java @@ -2,6 +2,8 @@ package com.hedera.pbj.integration.test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.protobuf.ByteString; import com.google.protobuf.util.JsonFormat; @@ -11,8 +13,10 @@ import com.hedera.pbj.integration.AccountDetailsPbj; import com.hedera.pbj.integration.EverythingTestData; import com.hedera.pbj.runtime.io.buffer.BufferedData; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.pbj.runtime.io.stream.WritableStreamingData; import com.hedera.pbj.test.proto.pbj.Everything; +import com.hedera.pbj.test.proto.pbj.MessageWithBytesAndString; import com.hederahashgraph.api.proto.java.GetAccountDetailsResponse; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; @@ -119,4 +123,17 @@ public void everythingTest() throws Exception { String pbjJson = bout.toString(); assertEquals(protoCJson, pbjJson); } + + @Test + public void NullStringTest() throws Exception { + final String json = """ + { + "bytesField": "308201a2300d06092a86", + "stringField": null + } + """; + MessageWithBytesAndString bytesAndString = MessageWithBytesAndString.JSON.parse(Bytes.wrap(json)); + assertNotEquals(0, bytesAndString.bytesField().length()); + assertTrue(bytesAndString.stringField().isEmpty()); + } }