Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ private static String generateTestMethod(final String modelClassName, final Stri
final var dataBuffer2 = getThreadLocalDataBuffer2();
final var byteBuffer = getThreadLocalByteBuffer();
final var charBuffer = getThreadLocalCharBuffer();
final var charBuffer2 = getThreadLocalCharBuffer2();
final var dataBufferLarge = getThreadLocalBigBuffer();

// model to bytes with PBJ
$modelClassName.PROTOBUF.write(modelObj, dataBuffer);
Expand Down Expand Up @@ -377,17 +377,30 @@ private static String generateTestMethod(final String modelClassName, final Stri
dataBuffer3.getBytes(0, readBytes);
assertArrayEquals(bytes.toByteArray(), readBytes);

// Test JSON Writing
final CharBufferToWritableSequentialData charBufferToWritableSequentialData = new CharBufferToWritableSequentialData(charBuffer);
$modelClassName.JSON.write(modelObj,charBufferToWritableSequentialData);
// Write JSON with PBJ
$modelClassName.JSON.write(modelObj,dataBufferLarge);
dataBufferLarge.flip();
final byte[] pbjJsonBytes = new byte[(int)dataBufferLarge.length()];
dataBufferLarge.getBytes(0, pbjJsonBytes);
final String pbjJsonString = new String(pbjJsonBytes, StandardCharsets.UTF_8);
// Write JSON with ProtoC
JsonFormat.printer().appendTo(protoCModelObj, charBuffer);
charBuffer.flip();
JsonFormat.printer().appendTo(protoCModelObj, charBuffer2);
charBuffer2.flip();
assertEquals(charBuffer2, charBuffer);
final String jsonString = charBuffer.toString();
final byte[] protoCJsonBytes = jsonString.getBytes(StandardCharsets.UTF_8);
// compare JSON string then bytes
assertEquals(jsonString, pbjJsonString);
assertArrayEquals(protoCJsonBytes, pbjJsonBytes);

// Test JSON Reading
final $modelClassName jsonReadPbj = $modelClassName.JSON.parse(JsonTools.parseJson(charBuffer), false, Integer.MAX_VALUE);
try{
final $modelClassName jsonReadPbj = $modelClassName.JSON.parse(BufferedData.wrap(protoCJsonBytes
), false, Integer.MAX_VALUE);
assertEquals(modelObj, jsonReadPbj);
} catch (Exception e) {
System.err.println("JSON read: " + jsonString);
throw e;
}
}

@SuppressWarnings("EqualsWithItself")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.hedera.pbj.compiler.impl.generators.Generator;
import com.hedera.pbj.compiler.impl.grammar.Protobuf3Parser;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -60,6 +61,7 @@ public void generate(
writer.addImport("com.hedera.pbj.runtime.*");
writer.addImport("com.hedera.pbj.runtime.io.*");
writer.addImport("com.hedera.pbj.runtime.io.buffer.*");
writer.addImport("com.hedera.pbj.runtime.json.JsonLexer");
writer.addImport("java.io.IOException");
writer.addImport("java.nio.*");
writer.addImport("java.nio.charset.*");
Expand All @@ -77,6 +79,7 @@ public void generate(
* JSON Codec for $modelClass model object. Generated based on protobuf schema.
*/
public final$staticModifier class $codecClass implements JsonCodec<$modelClass> {
$fieldNameConstants

/**
* Empty constructor
Expand All @@ -90,6 +93,7 @@ public void generate(
$writeMethod

"""
.replace("$fieldNameConstants", generateFieldNameConstants(fields))
.replace("$modelClass", modelClassName)
.replace("$staticModifier", staticModifier)
.replace("$codecClass", codecClassName)
Expand All @@ -108,6 +112,79 @@ public void generate(
writer.append("}");
}

/**
* Generates the field name constants for the fields in the message. The field names are converted to JSON field
* names then UTF-8 encoded as byte arrays. The byte arrays are then stored as static final fields in the generated
* JSON codec class. This is done to avoid all the conversion logic at runtime.
*
* @param fields the list of fields in the message
* @return a CharSequence containing the field name constants
*/
private CharSequence generateFieldNameConstants(List<Field> fields) {
final StringBuilder sb = new StringBuilder();
for (final var field : fields) {
// check if field is oneof
if (field instanceof OneOfField) {
// handle one of child fields
final OneOfField oneOfField = (OneOfField) field;
for (final var childField : oneOfField.fields()) {
sb.append(" private static final byte[] ");
sb.append(getFieldNameConstantName(childField));
sb.append(" = new byte[] {");
final String filedNameText = '"' + toJsonFieldName(childField.name()) + "\": ";
final byte[] fieldNameBytes = filedNameText.getBytes(StandardCharsets.UTF_8);
for (int i = 0; i < fieldNameBytes.length; i++) {
int b = fieldNameBytes[i] & 0xFF;
if (fieldNameBytes[i] < 0) {
sb.append("(byte)");
}
sb.append(String.format("0x%02X", b));
if (i < fieldNameBytes.length - 1) {
sb.append(", ");
}
}
sb.append("};\n");
}
} else {
sb.append(" private static final byte[] ");
sb.append(getFieldNameConstantName(field));
sb.append(" = new byte[] {");
final String filedNameText = '"' + toJsonFieldName(field.name()) + "\": ";
final byte[] fieldNameBytes = filedNameText.getBytes(StandardCharsets.UTF_8);
for (int i = 0; i < fieldNameBytes.length; i++) {
int b = fieldNameBytes[i] & 0xFF;
if (fieldNameBytes[i] < 0) {
sb.append("(byte)");
}
sb.append(String.format("0x%02X", b));
if (i < fieldNameBytes.length - 1) {
sb.append(", ");
}
}
sb.append("};\n");
}
}
// remove the first line indent
if (!sb.isEmpty()) {
sb.delete(0, 4);
}
return sb;
}

/**
* Generates the constant name for a field name.
*
* @param field the field
* @return the constant name for the field name
*/
static String getFieldNameConstantName(Field field) {
// check if the field name is not snake case but camel case or pascal case, if so convert it to snake case
final String fieldName = field.name().replaceAll("([a-z])([A-Z])", "$1_$2")
.replaceAll("([A-Z])([A-Z][a-z])", "$1_$2")
.toUpperCase();
return "FIELD_NAME_" + fieldName;
}

/**
* Converts a field name to a JSON field name.
*
Expand Down
Loading
Loading