mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-10-01 10:40:38 +03:00
Jval migration made functional
This commit is contained in:
@@ -40,8 +40,6 @@ public class EntityIO{
|
||||
this.serializer = serializer;
|
||||
this.name = name;
|
||||
|
||||
json.setIgnoreUnknownFields(true);
|
||||
|
||||
directory.mkdirs();
|
||||
|
||||
//load old revisions
|
||||
|
||||
@@ -10,7 +10,6 @@ import arc.util.*;
|
||||
import arc.util.CommandHandler.*;
|
||||
import arc.util.io.*;
|
||||
import arc.util.serialization.*;
|
||||
import arc.util.serialization.JsonValue.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.audio.*;
|
||||
@@ -434,8 +433,8 @@ public class NetClient implements ApplicationListener{
|
||||
public static void setRule(String rule, String jsonData){
|
||||
try{
|
||||
//readField searches for the specified value, so create a fake parent for it.
|
||||
tmpJsonMap.child = null;
|
||||
tmpJsonMap.addChild(rule, new JsonReader().parse(jsonData));
|
||||
tmpJsonMap.clear().put(rule, Jval.read(jsonData));
|
||||
|
||||
JsonIO.json.readField(state.rules, rule, tmpJsonMap);
|
||||
}catch(Throwable error){
|
||||
Log.err("Failed to read rule", error);
|
||||
|
||||
@@ -150,8 +150,14 @@ public class MapPatchesView implements AssetView{
|
||||
int countFields(Jval value){
|
||||
if(value.isObject() || value.isArray()){
|
||||
int sum = 0;
|
||||
for(var child : value){
|
||||
sum += countFields(child);
|
||||
if(value.isObject()){
|
||||
for(var child : value.asObject()){
|
||||
sum += countFields(child.value);
|
||||
}
|
||||
}else{
|
||||
for(var child : value.asArray()){
|
||||
sum += countFields(child);
|
||||
}
|
||||
}
|
||||
return Math.max(sum, 1);
|
||||
}else{
|
||||
|
||||
@@ -427,8 +427,8 @@ public class Rules{
|
||||
|
||||
@Override
|
||||
public void read(Json json, Jval jsonData){
|
||||
for(Jval value : jsonData){
|
||||
values[Integer.parseInt(value.name)] = json.readValue(TeamRule.class, value);
|
||||
for(var entry : jsonData.asObject()){
|
||||
values[Integer.parseInt(entry.key)] = json.readValue(TeamRule.class, entry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import arc.util.serialization.*;
|
||||
import arc.util.serialization.Json.*;
|
||||
import arc.util.serialization.Jval.*;
|
||||
import mindustry.*;
|
||||
import mindustry.audio.*;
|
||||
import mindustry.content.*;
|
||||
@@ -25,7 +26,7 @@ public class JsonIO{
|
||||
public void writeValue(Object value, Class knownType, Class elementType){
|
||||
if(value instanceof MappableContent c){
|
||||
try{
|
||||
getWriter().value(c.name);
|
||||
writer.value(c.name);
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -56,7 +57,7 @@ public class JsonIO{
|
||||
}
|
||||
|
||||
public static <T> T readBytes(Class<T> type, Class<?> elementType, DataInputStream input) throws IOException{
|
||||
return json.readValue(type, elementType, new UBJsonReader().parseWihoutClosing(input));
|
||||
return json.readValue(type, elementType, UBJson.read(input));
|
||||
}
|
||||
|
||||
public static String write(Object object){
|
||||
@@ -77,12 +78,12 @@ public class JsonIO{
|
||||
}
|
||||
|
||||
public static <T> T read(T base, String string){
|
||||
json.readFields(base, new JsonReader().parse(string.replace("io.anuke.", "")));
|
||||
json.readFields(base, Jval.read(string.replace("io.anuke.", "")));
|
||||
return base;
|
||||
}
|
||||
|
||||
public static String print(String in){
|
||||
return json.prettyPrint(in);
|
||||
return Jval.read(in).toString(Jformat.hjson);
|
||||
}
|
||||
|
||||
public static void classTag(String tag, Class<?> type){
|
||||
@@ -312,7 +313,7 @@ public class JsonIO{
|
||||
public MapObjectives read(Json json, Jval data, Class type){
|
||||
var exec = new MapObjectives();
|
||||
// First iteration to instantiate the objectives.
|
||||
for(var value = data.child; value != null; value = value.next){
|
||||
for(var value : data.asArray()){
|
||||
//glenn why did you implement this in the least backwards compatible way possible
|
||||
//the old objectives had lowercase class tags, now they're uppercase and either way I can't deserialize them without errors
|
||||
if(value.has("class") && Character.isLowerCase(value.getString("class").charAt(0))){
|
||||
@@ -333,8 +334,8 @@ public class JsonIO{
|
||||
|
||||
// Second iteration to map the parents.
|
||||
int i = 0;
|
||||
for(var value = data.child; value != null; value = value.next, i++){
|
||||
for(var parent = value.get("parents").child; parent != null; parent = parent.next){
|
||||
for(var entry : data.asArray()){
|
||||
for(var parent : entry.get("parents").asArray()){
|
||||
int val = parent.asInt();
|
||||
if(val >= 0 && val < exec.all.size){
|
||||
exec.all.get(i).parents.add(exec.all.get(val));
|
||||
|
||||
@@ -67,7 +67,7 @@ public class LogicScript implements JsonSerializable{
|
||||
public void read(Json json, Jval jsonData){
|
||||
if(jsonData.isObject()){
|
||||
timeout = Math.min(maxTimeoutMs, jsonData.getInt("timeout", 0));
|
||||
resetVars = jsonData.getBoolean("resetVars", false);
|
||||
resetVars = jsonData.getBool("resetVars", false);
|
||||
script = jsonData.getString("script", "");
|
||||
}else{
|
||||
script = jsonData.asString();
|
||||
|
||||
@@ -366,7 +366,7 @@ public class Maps{
|
||||
if(groups == null) return "[]";
|
||||
|
||||
StringWriter buffer = new StringWriter();
|
||||
JsonIO.json.setWriter(new JsonWriter(buffer));
|
||||
JsonIO.json.setWriter(new StringJsonWriter(buffer));
|
||||
|
||||
JsonIO.json.writeArrayStart();
|
||||
for(int i = 0; i < groups.size; i++){
|
||||
|
||||
@@ -96,9 +96,9 @@ public class ContentParser{
|
||||
throw new IllegalArgumentException("Attribute definitions must be objects, e.g. {heat: 10}");
|
||||
}
|
||||
Attributes attr = new Attributes();
|
||||
for(var child : data){
|
||||
Attribute value = Attribute.exists(child.name) ? Attribute.get(child.name) : Attribute.add(child.name);
|
||||
attr.set(value, child.asFloat());
|
||||
for(var entry : data.asObject()){
|
||||
Attribute value = Attribute.exists(entry.key) ? Attribute.get(entry.key) : Attribute.add(entry.key);
|
||||
attr.set(value, entry.value.asFloat());
|
||||
}
|
||||
return attr;
|
||||
});
|
||||
@@ -127,7 +127,7 @@ public class ContentParser{
|
||||
}
|
||||
}
|
||||
TextureRegion result = Core.atlas.find(str);
|
||||
if(!result.found()){
|
||||
if(!result.found() && !str.equalsIgnoreCase("error")){
|
||||
warn("Sprite not found: '" + str + "'");
|
||||
}
|
||||
return result;
|
||||
@@ -242,21 +242,18 @@ public class ContentParser{
|
||||
|
||||
//no singular operation, check for multi-operation
|
||||
if(opval == null){
|
||||
Jval opsVal =
|
||||
data.has("operations") ? data.get("operations") :
|
||||
data.has("ops") ? data.get("ops") : null;
|
||||
var opsVal =
|
||||
data.has("operations") ? data.get("operations").asArray() :
|
||||
data.has("ops") ? data.get("ops").asArray() : null;
|
||||
|
||||
if(opsVal != null){
|
||||
if(!opsVal.isArray()) throw new RuntimeException("Chained PartProgress operations must be an array.");
|
||||
int i = 0;
|
||||
while(true){
|
||||
for(int i = 0; i < opsVal.size; i ++){
|
||||
Jval val = opsVal.get(i);
|
||||
if(val == null) break;
|
||||
Jval op = val.has("operation") ? val.get("operation") :
|
||||
val.has("op") ? val.get("op") : null;
|
||||
|
||||
base = parseProgressOp(base, op.asString(), val);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,21 +281,21 @@ public class ContentParser{
|
||||
}
|
||||
|
||||
//transform array format
|
||||
if(data.isArray() && data.size == 3){
|
||||
if(data.isArray() && data.asArray().size == 3){
|
||||
return new Mat3D().setToTranslation(new Vec3(data.asFloatArray()));
|
||||
}
|
||||
|
||||
Mat3D mat = new Mat3D();
|
||||
|
||||
//TODO this is kinda bad
|
||||
for(var val : data){
|
||||
switch(val.name){
|
||||
for(var entry : data.asObject()){
|
||||
switch(entry.key){
|
||||
case "translate", "trans" -> mat.translate(parser.readValue(Vec3.class, data));
|
||||
case "scale", "scl" -> mat.scale(parser.readValue(Vec3.class, data));
|
||||
case "rotate", "rot" -> mat.rotate(parser.readValue(Vec3.class, data), data.getFloat("degrees", 0f));
|
||||
case "multiply", "mul" -> mat.mul(parser.readValue(Mat3D.class, data));
|
||||
case "x", "y", "z" -> {}
|
||||
default -> throw new RuntimeException("Unknown matrix transformation: '" + val.name + "'");
|
||||
default -> throw new RuntimeException("Unknown matrix transformation: '" + entry.key + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,7 +451,7 @@ public class ContentParser{
|
||||
//try to parse env bits
|
||||
if((type == int.class || type == Integer.class) && jsonData.isArray()){
|
||||
int value = 0;
|
||||
for(var str : jsonData){
|
||||
for(var str : jsonData.asArray()){
|
||||
if(!str.isString()) throw new SerializationException("Integer bitfield values must all be strings. Found: " + str);
|
||||
String field = str.asString();
|
||||
value |= Reflect.<Integer>get(Env.class, field);
|
||||
@@ -467,7 +464,7 @@ public class ContentParser{
|
||||
if(type == ItemStack.class && jsonData.isString() && jsonData.asString().contains("/")){
|
||||
String[] split = jsonData.asString().split("/");
|
||||
|
||||
return (T)fromJson(ItemStack.class, "{item: " + split[0] + ", amount: " + split[1] + "}");
|
||||
return (T)new ItemStack(fromJson(Item.class, split[0]), Integer.parseInt(split[1]));
|
||||
}
|
||||
|
||||
//try to parse "payloaditem/amount" syntax
|
||||
@@ -490,8 +487,9 @@ public class ContentParser{
|
||||
}
|
||||
|
||||
//try to parse Rect as array
|
||||
if(type == Rect.class && jsonData.isArray() && jsonData.size == 4){
|
||||
return (T)new Rect(jsonData.get(0).asFloat(), jsonData.get(1).asFloat(), jsonData.get(2).asFloat(), jsonData.get(3).asFloat());
|
||||
if(type == Rect.class && jsonData.isArray() && jsonData.asArray().size == 4){
|
||||
JsonArray arr = jsonData.asArray();
|
||||
return (T)new Rect(arr.get(0).asFloat(), arr.get(1).asFloat(), arr.get(2).asFloat(), arr.get(3).asFloat());
|
||||
}
|
||||
|
||||
//search across different content types to find one by name
|
||||
@@ -502,7 +500,7 @@ public class ContentParser{
|
||||
return found;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("\"" + jsonData.name + "\": No content found with name '" + jsonData.asString() + "'.");
|
||||
throw new IllegalArgumentException("No content found with name '" + jsonData.asString() + "'.");
|
||||
}
|
||||
|
||||
if(Content.class.isAssignableFrom(type)){
|
||||
@@ -513,7 +511,7 @@ public class ContentParser{
|
||||
T two = (T)Vars.content.getByName(ctype, jsonData.asString());
|
||||
|
||||
if(two != null) return two;
|
||||
throw new IllegalArgumentException((jsonData.name == null ? "" : "\"" + jsonData.name + "\": ") + "No " + ctype + " found with name '" + jsonData.asString() + "'.\nMake sure '" + jsonData.asString() + "' is spelled correctly, and that it really exists!\nThis may also occur because its file failed to parse.");
|
||||
throw new IllegalArgumentException("No " + ctype + " found with name '" + jsonData.asString() + "'.\nMake sure '" + jsonData.asString() + "' is spelled correctly, and that it really exists!\nThis may also occur because its file failed to parse.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,8 +520,11 @@ public class ContentParser{
|
||||
};
|
||||
|
||||
public void readBlockConsumers(Block block, Jval value){
|
||||
for(Jval child : value){
|
||||
switch(child.name){
|
||||
for(var entry : value.asObject()){
|
||||
String name = entry.key;
|
||||
Jval child = entry.value;
|
||||
|
||||
switch(name){
|
||||
case "remove" -> {
|
||||
String[] values = child.isString() ? new String[]{child.asString()} : child.asStringArray();
|
||||
for(String type : values){
|
||||
@@ -569,7 +570,7 @@ public class ContentParser{
|
||||
}
|
||||
}
|
||||
case "powerBuffered" -> block.consumePowerBuffered(child.asFloat());
|
||||
default -> throw new IllegalArgumentException("Unknown consumption type: '" + child.name + "' for block '" + block.name + "'.");
|
||||
default -> throw new IllegalArgumentException("Unknown consumption type: '" + name + "' for block '" + block.name + "'.");
|
||||
}
|
||||
}
|
||||
value.remove("consumes");
|
||||
@@ -1024,15 +1025,10 @@ public class ContentParser{
|
||||
public Content parse(LoadedMod mod, String name, String json, Fi file, ContentType type) throws Exception{
|
||||
checkInit();
|
||||
|
||||
//remove extra # characters to make it valid json... apparently some people have *unquoted* # characters in their json
|
||||
if(file.extension().equals("json")){
|
||||
json = json.replace("#", "\\#");
|
||||
}
|
||||
|
||||
currentFile = file;
|
||||
currentMod = mod;
|
||||
|
||||
var rawValue = parser.fromJson(null, Jval.read(json).toString(Jformat.plain));
|
||||
var rawValue = parser.fromJson(null, json);
|
||||
if(!(rawValue instanceof Jval value)) throw new SerializationException("Content JSON must be an object, not a single value.");
|
||||
|
||||
if(!parsers.containsKey(type)){
|
||||
@@ -1113,12 +1109,7 @@ public class ContentParser{
|
||||
}
|
||||
|
||||
private GenericMesh[] parseMeshes(Planet planet, Jval array){
|
||||
var res = new GenericMesh[array.size];
|
||||
for(int i = 0; i < array.size; i++){
|
||||
//yes get is O(n) but it's practically irrelevant here
|
||||
res[i] = parseMesh(planet, array.get(i));
|
||||
}
|
||||
return res;
|
||||
return array.asArray().map(value -> parseMesh(planet, value)).toArray(GenericMesh.class);
|
||||
}
|
||||
|
||||
private GenericMesh parseMesh(Planet planet, Jval data){
|
||||
@@ -1274,15 +1265,18 @@ public class ContentParser{
|
||||
toBeParsed.remove(object);
|
||||
var type = object.getClass();
|
||||
var fields = parser.getFields(type);
|
||||
for(Jval child = jsonMap.child; child != null; child = child.next){
|
||||
FieldMetadata metadata = fields.get(child.name().replace(" ", "_"));
|
||||
for(var entry : jsonMap.asObject()){
|
||||
String name = entry.key;
|
||||
Jval child = entry.value;
|
||||
|
||||
FieldMetadata metadata = fields.get(name.replace(" ", "_"));
|
||||
if(metadata == null){
|
||||
if(ignoreUnknownFields){
|
||||
warn("@Unknown field '@' for class '@'", currentContent == null ? "" : "[" + currentContent.minfo.sourceFile.name() + "]: ", child.name, type.getSimpleName());
|
||||
warn("@Unknown field '@' for class '@'", currentContent == null ? "" : "[" + currentContent.minfo.sourceFile.name() + "]: ", name, type.getSimpleName());
|
||||
continue;
|
||||
}else{
|
||||
SerializationException ex = new SerializationException("Field not found: " + child.name + " (" + type.getName() + ")");
|
||||
ex.addTrace(child.trace());
|
||||
SerializationException ex = new SerializationException("Field not found: " + name + " (" + type.getName() + ")");
|
||||
ex.addTrace(child.toString());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
@@ -1301,7 +1295,7 @@ public class ContentParser{
|
||||
}
|
||||
}else{
|
||||
boolean isMap = ObjectMap.class.isAssignableFrom(field.getType()) || ObjectIntMap.class.isAssignableFrom(field.getType()) || ObjectFloatMap.class.isAssignableFrom(field.getType());
|
||||
boolean mergeMap = isMap && child.has("add") && child.get("add").isBoolean() && child.getBoolean("add", false);
|
||||
boolean mergeMap = isMap && child.has("add") && child.get("add").isBoolean() && child.getBool("add", false);
|
||||
|
||||
if(mergeMap){
|
||||
child.remove("add");
|
||||
@@ -1330,7 +1324,7 @@ public class ContentParser{
|
||||
throw ex;
|
||||
}catch(RuntimeException runtimeEx){
|
||||
SerializationException ex = new SerializationException(runtimeEx);
|
||||
ex.addTrace(child.trace());
|
||||
ex.addTrace(child.toString());
|
||||
ex.addTrace(field.getName() + " (" + type.getName() + ")");
|
||||
throw ex;
|
||||
}
|
||||
@@ -1370,8 +1364,10 @@ public class ContentParser{
|
||||
currentMod = cur;
|
||||
currentFile = file;
|
||||
|
||||
boolean isObject = research.isObject();
|
||||
|
||||
//add custom objectives
|
||||
if(research.has("objectives")){
|
||||
if(isObject && research.has("objectives")){
|
||||
node.objectives.addAll(parser.readValue(Objective[].class, research.get("objectives")));
|
||||
}
|
||||
|
||||
@@ -1389,13 +1385,13 @@ public class ContentParser{
|
||||
node.setupRequirements(unlock.researchRequirements());
|
||||
}
|
||||
|
||||
if(research.has("planet")){
|
||||
if(isObject && research.has("planet")){
|
||||
node.planet = find(ContentType.planet, research.getString("planet"));
|
||||
}
|
||||
|
||||
if(research.getBoolean("root", false)){
|
||||
if(isObject && research.getBool("root", false)){
|
||||
node.name = research.getString("name", unlock.name);
|
||||
node.requiresUnlock = research.getBoolean("requiresUnlock", false);
|
||||
node.requiresUnlock = research.getBool("requiresUnlock", false);
|
||||
TechTree.roots.add(node);
|
||||
}else{
|
||||
if(researchName != null){
|
||||
|
||||
@@ -249,8 +249,8 @@ public class DataPatcher{
|
||||
|
||||
set.name = value.getString("name", "");
|
||||
value.remove("name"); //patchsets can have a name, ignore it if present
|
||||
for(var child : value){
|
||||
assign(root, child.name, child, null, null, null);
|
||||
for(var entry : value.asObject()){
|
||||
assign(root, entry.key, entry.value, null, null, null);
|
||||
}
|
||||
currentlyApplyingPatch = null;
|
||||
|
||||
@@ -413,8 +413,8 @@ public class DataPatcher{
|
||||
|
||||
if(object == root){
|
||||
if(value instanceof Jval jval && jval.isObject()){
|
||||
for(var child : jval){
|
||||
assign(root, field + "." + child.name, child, null, null, null);
|
||||
for(var entry : jval.asObject()){
|
||||
assign(root, field + "." + entry.key, entry.value, null, null, null);
|
||||
}
|
||||
}else{
|
||||
warn("Content '@' cannot be assigned.", field);
|
||||
@@ -661,14 +661,14 @@ public class DataPatcher{
|
||||
//assign each field manually
|
||||
var childFields = parser.getJson().getFields(prevValue.getClass().isAnonymousClass() ? prevValue.getClass().getSuperclass() : prevValue.getClass());
|
||||
|
||||
for(var child : jsv){
|
||||
if(child.name != null){
|
||||
assign(prevValue, child.name, child,
|
||||
for(var entry : jsv.asObject()){
|
||||
if(entry.key != null){
|
||||
assign(prevValue, entry.key, entry.value,
|
||||
metadata != null && (metadata.type == ObjectMap.class || metadata.type == ObjectFloatMap.class) ? metadata :
|
||||
metadata != null && metadata.type == Seq.class ? new FieldData(metadata.elementType, null, null) :
|
||||
metadata != null && metadata.type.isArray() ? new FieldData(metadata.type.getComponentType(), null, null) :
|
||||
!childFields.containsKey(child.name) ? null :
|
||||
new FieldData(childFields.get(child.name)), object, field);
|
||||
!childFields.containsKey(entry.key) ? null :
|
||||
new FieldData(childFields.get(entry.key)), object, field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,6 @@ public class PatchAsset extends DataAsset{
|
||||
@Override
|
||||
public String toString(){
|
||||
//the json can be a single 'error' value if it failed to parse
|
||||
return !json.isObject() ? patch : json.prettyPrint(OutputType.minimal, 2);
|
||||
return !json.isObject() ? patch : json.toString(Jformat.hjson);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,14 @@ public class MapLocales extends ObjectMap<String, StringMap> implements JsonSeri
|
||||
|
||||
@Override
|
||||
public void read(Json json, Jval jsonData){
|
||||
for(Jval value : jsonData){
|
||||
for(var entry : jsonData.asObject()){
|
||||
StringMap map = new StringMap();
|
||||
|
||||
for(Jval child = value.child; child != null; child = child.next){
|
||||
map.put(child.name, json.readValue(String.class, null, child));
|
||||
for(var innerEntry : entry.value.asObject()){
|
||||
map.put(innerEntry.key, innerEntry.value.asString());
|
||||
}
|
||||
|
||||
put(value.name, map);
|
||||
put(entry.key, map);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
Boolp allowMusic = () -> !rules.disableMusic;
|
||||
Func<String, Seq<MusicContainer>> parser = str -> {
|
||||
try{
|
||||
return Seq.map(new JsonReader().parse("[" + str + "]").asStringArray(), MusicContainer::new);
|
||||
return Jval.read("[" + str + "]").asArray().map( j -> new MusicContainer(j.asString()));
|
||||
}catch(Throwable e){
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,4 +26,4 @@ org.gradle.caching=true
|
||||
org.gradle.internal.http.socketTimeout=100000
|
||||
org.gradle.internal.http.connectionTimeout=100000
|
||||
android.enableR8.fullMode=false
|
||||
archash=c2632c8f1b
|
||||
archash=36256993a0
|
||||
|
||||
@@ -6,6 +6,7 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import arc.util.serialization.*;
|
||||
import arc.util.serialization.Jval.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
@@ -231,7 +232,7 @@ public class ApplicationTests{
|
||||
for(String file : files){
|
||||
try{
|
||||
String str = Core.files.absolute("./../../" + file).readString();
|
||||
assertEquals(ValueType.array, new JsonReader().parse(str).type());
|
||||
assertEquals(Jtype.array, Jval.read(str).getType());
|
||||
assertTrue(Jval.read(str).isArray());
|
||||
JSONArray array = new JSONArray(str);
|
||||
assertTrue(array.length() > 0);
|
||||
|
||||
Reference in New Issue
Block a user