Skip to content

Commit 436b396

Browse files
authored
#466 Convert JSON object to CBORMetadataMap and JSON Array to CBORMetadataList / New methods in MetadataBuilder (#467)
* #466 Make json object --> CBOR Map and json array --> CBOR Array methods public and handle null value * #466 fixed tests * #466 Handle BigInteger encoded as ByteString * Add unit tests and improve metadata JSON handling Added several new unit tests for MetadataBuilder and improved the handling of byte strings in the metadata converter. Also included multiple new test JSON files to cover different metadata scenarios. * To fix Sonarcloud warnings
1 parent c3d865f commit 436b396

File tree

12 files changed

+648
-21
lines changed

12 files changed

+648
-21
lines changed

metadata/src/main/java/com/bloxbean/cardano/client/metadata/MetadataBuilder.java

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,47 @@
33
import com.bloxbean.cardano.client.metadata.cbor.CBORMetadata;
44
import com.bloxbean.cardano.client.metadata.cbor.CBORMetadataList;
55
import com.bloxbean.cardano.client.metadata.cbor.CBORMetadataMap;
6+
import com.bloxbean.cardano.client.metadata.helper.JsonNoSchemaToMetadataConverter;
7+
import com.bloxbean.cardano.client.metadata.helper.MetadataToJsonNoSchemaConverter;
8+
import com.bloxbean.cardano.client.util.JsonUtil;
9+
import com.fasterxml.jackson.core.JsonProcessingException;
10+
import com.fasterxml.jackson.databind.*;
11+
import com.fasterxml.jackson.databind.node.ArrayNode;
12+
import com.fasterxml.jackson.databind.node.ObjectNode;
13+
14+
import java.math.BigInteger;
615

716
/**
8-
* Builder class to create Metadata, MetadataMap and MetadataList
17+
* The {@code MetadataBuilder} class provides a collection of static methods for creating
18+
* and manipulating metadata objects in various formats such as CBOR and JSON.
19+
* It offers functionality to create, deserialize, and convert metadata
20+
* between JSON and CBOR representations.
21+
*
22+
* <p> This class serves as a utility for handling metadata operations, abstracting
23+
* the complexity of different metadata types and conversion processes behind
24+
* simple method calls. It includes methods for:
25+
* <ul>
26+
* <li>Creating different types of metadata objects like {@code Metadata}, {@code MetadataMap}, {@code MetadataList}.</li>
27+
* <li>Deserialization of CBOR byte arrays to Metadata objects.</li>
28+
* <li>Conversion of JSON strings to Metadata and vice-versa.</li>
29+
* </ul>
30+
*
31+
* <p> Example usage:
32+
* <pre>
33+
* {@code
34+
* Metadata metadata = MetadataBuilder.createMetadata();
35+
* MetadataMap metadataMap = MetadataBuilder.createMap();
36+
* MetadataList metadataList = MetadataBuilder.createList();
37+
* Metadata fromJson = MetadataBuilder.metadataFromJson(jsonString);
38+
* String jsonString = MetadataBuilder.toJson(metadata);
39+
* }
40+
* </pre>
941
*/
1042
public class MetadataBuilder {
1143

1244
/**
1345
* Create Metadata object
46+
*
1447
* @return Metadata
1548
*/
1649
public static Metadata createMetadata() {
@@ -19,6 +52,7 @@ public static Metadata createMetadata() {
1952

2053
/**
2154
* Create MetadataMap object
55+
*
2256
* @return MetadataMap
2357
*/
2458
public static MetadataMap createMap() {
@@ -27,9 +61,122 @@ public static MetadataMap createMap() {
2761

2862
/**
2963
* Create MetadataList object
64+
*
3065
* @return MetadataList
3166
*/
3267
public static MetadataList createList() {
3368
return new CBORMetadataList();
3469
}
70+
71+
/**
72+
* Deserialize cbor bytes to Metadata object
73+
*
74+
* @param cborBytes
75+
* @return Metadata
76+
*/
77+
public static Metadata deserialize(byte[] cborBytes) {
78+
return CBORMetadata.deserialize(cborBytes);
79+
}
80+
81+
/**
82+
* Converts a JSON string to a Metadata object.
83+
*
84+
* @param json the JSON string to be converted to a Metadata object
85+
* @return a Metadata object generated from the provided JSON string
86+
* @throws IllegalArgumentException if the JSON format is invalid
87+
*/
88+
public static Metadata metadataFromJson(String json) {
89+
try {
90+
return JsonNoSchemaToMetadataConverter.jsonToCborMetadata(json);
91+
} catch (Exception e) {
92+
throw new IllegalArgumentException("Invalid json format for metadta", e);
93+
}
94+
}
95+
96+
/**
97+
* Creates a Metadata object from a JSON string.
98+
*
99+
* @param label a BigInteger representing the label associated with the metadata
100+
* @param jsonBody a JSON string representing the metadata content
101+
* @return a Metadata object containing the parsed data
102+
* @throws IllegalArgumentException if the JSON format is invalid or not an object/array
103+
*/
104+
public static Metadata metadataFromJsonBody(BigInteger label, String jsonBody) {
105+
JsonNode jsonNode;
106+
try {
107+
jsonNode = JsonUtil.parseJson(jsonBody);
108+
} catch (JsonProcessingException e) {
109+
throw new IllegalArgumentException("Invalid json format");
110+
}
111+
112+
if (jsonNode instanceof ObjectNode) {
113+
var metadataMap = JsonNoSchemaToMetadataConverter.parseObjectNode((ObjectNode) jsonNode);
114+
return new CBORMetadata().put(label, metadataMap);
115+
} else if (jsonNode.isArray()) {
116+
var metadataList = JsonNoSchemaToMetadataConverter.parseArrayNode((ArrayNode) jsonNode);
117+
return new CBORMetadata().put(label, metadataList);
118+
} else {
119+
throw new IllegalArgumentException("Invalid json format. Should be an object or an array");
120+
}
121+
}
122+
123+
/**
124+
* Parses a JSON string to create a MetadataMap instance.
125+
*
126+
* @param jsonBody the JSON string to be converted to a MetadataMap object
127+
* @return a MetadataMap object generated from the provided JSON object
128+
* @throws IllegalArgumentException if the JSON format is invalid
129+
*/
130+
public static MetadataMap metadataMapFromJsonBody(String jsonBody) {
131+
try {
132+
return JsonNoSchemaToMetadataConverter.parseObjectNode((ObjectNode) JsonUtil.parseJson(jsonBody));
133+
} catch (JsonProcessingException e) {
134+
throw new IllegalArgumentException("Could not parse json body to MetadataMap", e);
135+
}
136+
}
137+
138+
/**
139+
* Converts a JSON string into a MetadataList instance.
140+
*
141+
* @param jsonBody the JSON string to be converted to a MetadataList object
142+
* @return a MetadataList object generated from the provided JSON array
143+
* @throws IllegalArgumentException if the JSON format is invalid or cannot be parsed
144+
*/
145+
public static MetadataList metadataListFromJsonBody(String jsonBody) {
146+
try {
147+
return JsonNoSchemaToMetadataConverter.parseArrayNode((ArrayNode) JsonUtil.parseJson(jsonBody));
148+
} catch (JsonProcessingException e) {
149+
throw new IllegalArgumentException("Could not parse json body to MetadataMap", e);
150+
}
151+
}
152+
153+
/**
154+
* Converts a Metadata object to its JSON representation.
155+
*
156+
* @param metadata the Metadata object to be converted
157+
* @return a JsonNode representing the JSON equivalent of the given Metadata
158+
* @throws IllegalArgumentException if the serialization results in an invalid JSON format
159+
*/
160+
public static String toJson(Metadata metadata) {
161+
try {
162+
return MetadataToJsonNoSchemaConverter.cborBytesToJson(metadata.serialize());
163+
} catch (Exception e) {
164+
throw new IllegalArgumentException("Invalid json format");
165+
}
166+
}
167+
168+
/**
169+
* Converts the provided CBOR byte array to a JSON string.
170+
*
171+
* @param serializedCbor A byte array serialized CBOR data to be converted to JSON.
172+
* @return A string containing the JSON representation of the given CBOR data.
173+
* @throws IllegalArgumentException if the CBOR data cannot be converted to a valid JSON format.
174+
*/
175+
public static String toJson(byte[] serializedCbor) {
176+
try {
177+
return MetadataToJsonNoSchemaConverter.cborBytesToJson(serializedCbor);
178+
} catch (Exception e) {
179+
throw new IllegalArgumentException("Invalid json format");
180+
}
181+
}
35182
}

metadata/src/main/java/com/bloxbean/cardano/client/metadata/cbor/MetadataHelper.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
@Slf4j
1010
public class MetadataHelper {
11+
private static final int BIG_UINT_TAG = 2;
12+
private static final int BIG_NINT_TAG = 3;
13+
private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1);
1114

1215
public static Object extractActualValue(DataItem dataItem) {
1316
if (dataItem instanceof UnicodeString) {
@@ -17,7 +20,7 @@ public static Object extractActualValue(DataItem dataItem) {
1720
} else if (dataItem instanceof NegativeInteger) {
1821
return ((NegativeInteger) dataItem).getValue();
1922
} else if (dataItem instanceof ByteString) {
20-
return ((ByteString) dataItem).getBytes();
23+
return parseByteString((ByteString) dataItem);
2124
} else if (dataItem instanceof Map) {
2225
return new CBORMetadataMap((Map) dataItem);
2326
} else if (dataItem instanceof Array) {
@@ -58,4 +61,16 @@ public static int checkLength(String str) {
5861
return str.getBytes(StandardCharsets.UTF_8).length;
5962
}
6063

64+
public static Object parseByteString(ByteString valueItem) {
65+
var tag = valueItem.getTag();
66+
if (tag != null){
67+
if (tag.getValue() == BIG_UINT_TAG) {
68+
return new BigInteger(1, valueItem.getBytes());
69+
} else if (tag.getValue() == BIG_NINT_TAG) {
70+
return MINUS_ONE.subtract(new BigInteger(1, valueItem.getBytes()));
71+
}
72+
}
73+
74+
return valueItem.getBytes();
75+
}
6176
}

metadata/src/main/java/com/bloxbean/cardano/client/metadata/helper/JsonNoSchemaToMetadataConverter.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
import com.fasterxml.jackson.core.JsonProcessingException;
1111
import com.fasterxml.jackson.databind.JsonNode;
1212
import com.fasterxml.jackson.databind.ObjectMapper;
13-
import com.fasterxml.jackson.databind.node.ArrayNode;
14-
import com.fasterxml.jackson.databind.node.NumericNode;
15-
import com.fasterxml.jackson.databind.node.ObjectNode;
16-
import com.fasterxml.jackson.databind.node.TextNode;
13+
import com.fasterxml.jackson.databind.node.*;
1714

1815
import java.math.BigInteger;
1916
import java.util.Iterator;
@@ -67,7 +64,7 @@ private static CBORMetadata parseTopJsonObject(ObjectNode objectNode) {
6764
return metadata;
6865
}
6966

70-
private static CBORMetadataList parseArrayNode(ArrayNode value) {
67+
public static CBORMetadataList parseArrayNode(ArrayNode value) {
7168
CBORMetadataList metadataList = new CBORMetadataList();
7269
Iterator<JsonNode> fields = value.elements();
7370

@@ -95,7 +92,7 @@ private static CBORMetadataList parseArrayNode(ArrayNode value) {
9592
return metadataList;
9693
}
9794

98-
private static CBORMetadataMap parseObjectNode(ObjectNode jsonObj) {
95+
public static CBORMetadataMap parseObjectNode(ObjectNode jsonObj) {
9996
CBORMetadataMap metadataMap = new CBORMetadataMap();
10097
Iterator<String> fields = jsonObj.fieldNames();
10198

@@ -104,7 +101,9 @@ private static CBORMetadataMap parseObjectNode(ObjectNode jsonObj) {
104101
JsonNode nd = jsonObj.get(field);
105102

106103
Object cborValue = processValueNode(nd);
107-
if (cborValue instanceof CBORMetadataMap) {
104+
if (cborValue == null) {
105+
metadataMap.put(field, (String)null);
106+
} else if (cborValue instanceof CBORMetadataMap) {
108107
metadataMap.put(field, (CBORMetadataMap) cborValue);
109108
} else if (cborValue instanceof CBORMetadataList) {
110109
metadataMap.put(field, (CBORMetadataList) cborValue);
@@ -148,6 +147,8 @@ private static Object processValueNode(JsonNode value) {
148147
} else if(value instanceof NumericNode) {
149148
BigInteger valueInt = ((NumericNode)value).bigIntegerValue();
150149
return valueInt;
150+
} else if (value instanceof NullNode) {
151+
return null;
151152
} else {
152153
throw new JsonMetadaException("Invalid value or value not recognized : " + value);
153154
}

metadata/src/main/java/com/bloxbean/cardano/client/metadata/helper/MetadataToJsonNoSchemaConverter.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
import co.nstant.in.cbor.CborDecoder;
44
import co.nstant.in.cbor.CborException;
55
import co.nstant.in.cbor.model.*;
6+
import co.nstant.in.cbor.model.Map;
67
import com.bloxbean.cardano.client.exception.CborDeserializationException;
8+
import com.bloxbean.cardano.client.metadata.cbor.MetadataHelper;
79
import com.bloxbean.cardano.client.metadata.exception.MetadataDeSerializationException;
810
import com.bloxbean.cardano.client.util.HexUtil;
911
import com.fasterxml.jackson.databind.ObjectMapper;
1012

11-
import java.util.ArrayList;
12-
import java.util.Collection;
13-
import java.util.HashMap;
14-
import java.util.List;
13+
import java.util.*;
1514

1615
import static co.nstant.in.cbor.model.MajorType.*;
1716

@@ -58,7 +57,7 @@ private static java.util.Map cborHexToJavaMap(String hex) throws CborDeserializa
5857
if(dataItemList != null && dataItemList.size() > 1)
5958
throw new MetadataDeSerializationException("Multiple DataItems found at top level. Should be zero : " + dataItemList.size());
6059

61-
java.util.Map result = new HashMap();
60+
java.util.Map result;
6261
DataItem dataItem = dataItemList.get(0);
6362
if(dataItem instanceof Map) {
6463
result = processMap((Map)dataItem);
@@ -69,7 +68,7 @@ private static java.util.Map cborHexToJavaMap(String hex) throws CborDeserializa
6968
}
7069

7170
private static java.util.Map processMap(Map map) {
72-
java.util.Map resultMap = new HashMap();
71+
java.util.Map resultMap = new LinkedHashMap();
7372
Collection<DataItem> keys = map.getKeys();
7473
for(DataItem keyItem: keys) {
7574
DataItem valueItem = map.get(keyItem);
@@ -87,8 +86,7 @@ private static Object processKey(DataItem keyItem) {
8786
} else if(NEGATIVE_INTEGER.equals(keyItem.getMajorType())) {
8887
return ((NegativeInteger) keyItem).getValue();
8988
} else if (BYTE_STRING.equals(keyItem.getMajorType())) {
90-
byte[] bytes = ((ByteString) keyItem).getBytes();
91-
return "0x" + HexUtil.encodeHexString(bytes);
89+
return byteStringToObject((ByteString) keyItem);
9290
} else if (UNICODE_STRING.equals(keyItem.getMajorType())) {
9391
return ((UnicodeString) keyItem).getString();
9492
} else {
@@ -97,13 +95,14 @@ private static Object processKey(DataItem keyItem) {
9795
}
9896

9997
private static Object processValue(DataItem valueItem) {
100-
if(UNSIGNED_INTEGER.equals(valueItem.getMajorType())){
98+
if (valueItem == SimpleValue.NULL) {
99+
return null;
100+
} else if(UNSIGNED_INTEGER.equals(valueItem.getMajorType())){
101101
return ((UnsignedInteger)valueItem).getValue();
102102
} else if(NEGATIVE_INTEGER.equals(valueItem.getMajorType())) {
103103
return ((NegativeInteger)valueItem).getValue();
104104
} else if(BYTE_STRING.equals(valueItem.getMajorType())) {
105-
byte[] bytes = ((ByteString)valueItem).getBytes();
106-
return "0x" + HexUtil.encodeHexString(bytes);
105+
return byteStringToObject((ByteString) valueItem);
107106
} else if(UNICODE_STRING.equals(valueItem.getMajorType())) {
108107
return ((UnicodeString)valueItem).getString();
109108
} else if(MAP.equals(valueItem.getMajorType())){
@@ -115,6 +114,16 @@ private static Object processValue(DataItem valueItem) {
115114
}
116115
}
117116

117+
private static Object byteStringToObject(ByteString valueItem) {
118+
var extractedValue = MetadataHelper.parseByteString(valueItem);
119+
if (extractedValue instanceof byte[]) {
120+
byte[] bytes = (byte[]) extractedValue;
121+
return "0x" + HexUtil.encodeHexString(bytes);
122+
} else {
123+
return extractedValue;
124+
}
125+
}
126+
118127
private static Object processArray(Array array) {
119128
List<DataItem> dataItems = array.getDataItems();
120129
List resultList = new ArrayList();

0 commit comments

Comments
 (0)