0

I want to be able to set enum/bit flags from Json. I have managed to serialize my object with a HashSet containing my enum values. By default LibGDX serializes this but adds a class field with the type of set to the json so it knows what to do. I want my json to be clean and decoupled from java so I wrote this class:

public class CriteriaSerializer implements Json.Serializer<HashSet> {

    @Override
    public void write(Json json, HashSet object, Class knownType) {
        json.writeArrayStart();
        for (Object o : object)
        {
            if (o instanceof Modifier.Criteria) {
                json.writeValue(o, Modifier.Criteria.class);
            }
        }
        json.writeArrayEnd();
    }

    @Override
    public HashSet read(Json json, JsonValue jsonData, Class type) {
        System.out.println("Running!?");
        HashSet<Modifier.Criteria> criteriaSet = new HashSet<Modifier.Criteria>();

        for (JsonValue entry = jsonData.child; entry != null; entry = entry.next)
        {
            criteriaSet.add(Modifier.Criteria.valueOf("ADD"));//Modifier.Criteria.valueOf(entry.asString()));
        }

        return criteriaSet;
    }
}

The write method results in the following output:

modifier: {
    amount: 1 //Other field
    criteriaSet: [
        RED
        BLUE
    ]

All I need is to get those values as strings so I can do something along the lines of myCriteriaSet.put(Criteria.valueOf(output). The thing is, the program crashes before the read method is running. I guess this is because it finds an ArrayList in the json data but the corresponding field in the object is a HashSet. This is the error java.lang.IllegalArgumentException: Can not set java.util.Set field com.buckriderstudio.towercrawler.Creature.Modifier.criteriaSet to java.util.ArrayList

Both writing to- and reading from json is important to me so I need them to work with eachother. In the end I'm just looking for a clean solution to get (de)serialize EnumSet or bit combinations in a readable manner. I feel I'm close but there might be a better technique then the one I'm trying.

What I like about the LibgDX Json implementation is that fields are not mandatory and can have default values. This cleans up the json data considerably since I have a lot of fields that can be set optionally. Therefor this library has my preference to say Jackson, but I have not played around with Jackson all that much though.

EDIT

This is an edit especially for Andreas. As far as I know (but I might be wrong) this has nothing to do with the actual problem. Andreas is explaining to me that the Json syntax is wrong, the fact is that it does not even reach my read method and that the json library that ships with LibGDX is not writing 100% correct Json. Does it need to? Perhaps to bear the name Json? Does it need to to work? I don't think so.

Here is my test. All I do is create this Creature object and parse it with 1 line of code. There is no personal code of me involved in parsing this.

Creature c = new Creature("MadMenyo");
System.out.println(json.prettyPrint(c));

//Output

{
name: MadMenyo
modifier: {
    amount: 1
    criteriaSet: {
        class: java.util.HashSet
        items: [
            VS_NATURE
            MULTIPLY
        ]
    }
}
stats: {
    ENDURANCE: {
        abbreviation: END
        displayName: Endurance
        baseValue: 8
        finalValue: 8
    }
    MAGIC: {
        abbreviation: MP
        displayName: Your mana
        baseValue: 20
        finalValue: 20
    }
    STRENGTH: {
        baseValue: 6
        finalValue: 6
    }
    HEALTH: {
        abbreviation: HP
        displayName: Your life
        baseValue: 100
        finalValue: 100
    }
}
}

//Looks like no valid Json to me. But the following line parses that correctly into a Creature object.

Creature jsonCreature = json.fromJson(Creature.class, jsonCreature);

Before we drift off even further. The reason why I do not want to use this is because it outputs the class class: java.util.HashSet and I'm pretty sure that is unnecessary.

EDIT

After adding the following lines of code I managed to output correct json. Yet the code still breaks before it gets to my custom read method. The question remain how to either fix that or serialize a Enumset or other Set holding enums in a different way as long as it is readable in Json and can be used as flags.

    JsonWriter jw = new JsonWriter(new StringWriter());
    json.setOutputType(JsonWriter.OutputType.json);
    json.setWriter(jw);

//Now outputs proper Json

{
"name": "MadMenyo",
"modifier": {
    "amount": 1,
    "criteriaSet": [
        "VS_NATURE",
        "MULTIPLY"
    ]
},
"stats": {
    "ENDURANCE": {
        "abbreviation": "END",
        "displayName": "Endurance",
        "baseValue": 8,
        "finalValue": 8
    },
    "MAGIC": {
        "abbreviation": "MP",
        "displayName": "Your mana",
        "baseValue": 20,
        "finalValue": 20
    },
    "STRENGTH": {
        "baseValue": 6,
        "finalValue": 6
    },
    "HEALTH": {
        "abbreviation": "HP",
        "displayName": "Your life",
        "baseValue": 100,
        "finalValue": 100
    }
}
Madmenyo
  • 8,389
  • 7
  • 52
  • 99
  • That is not valid [JSON](http://www.json.org/). It's missing 2 commas, and the array values needs to be quoted. Before going any further, make sure you understand the result your trying to get. – Andreas Dec 01 '16 at 21:37
  • @Andreas It's what my write method outputs. Cant really influence comma's and quotes afaik. – Madmenyo Dec 01 '16 at 21:39
  • You wrote the write method, so if it's producing the wrong result, then it's your job to fix it. You should try testing your output, e.g. on http://jsonlint.com/. If it fails, you're doing it wrong. Don't even try testing the `read` method until you get valid JSON, which means your `write` method must be fixed first. – Andreas Dec 01 '16 at 21:41
  • @Andreas I don't think I'm necessarily doing it wrong. I think it's the library. Standard serialization of criteriaSet looks like this: `criteriaSet: { class: java.util.HashSet items: [ RED BLUE ] }` which neither is correct json. – Madmenyo Dec 01 '16 at 21:45
  • @Andreas Yet I do not want to venture in a whole different discussion. So unless you can tell me what I'm doing with the current json library is impossible I will continue with the same question. – Madmenyo Dec 01 '16 at 21:46
  • The JSON is not valid, so if your `read` method fails, that's very likely why. Trying to find a way to make it read invalid input is focusing on the wrong thing, and a total waste of time. It's like saying: Sure there are no wheels on my bicycle, but how do I ride it anyway? It makes no sense. – Andreas Dec 01 '16 at 21:50
  • @Andreas No it's not, I'm actually even telling why it's not working in my question and also said that it's not even running the read method. So if you can start reading my question you might save me some time actually. – Madmenyo Dec 01 '16 at 21:53
  • @Andreas Don't get me wrong, I do agree that the JSON should be properly written. But I have no control over it. If I just use build in `json.toJson(myObject)` it's not even properly writing Json. That does not necessarily mean something does not work or is bad. There might be reasons for it but you have to ask that to the creators of LibGDX or at least the one who merged the Json library. – Madmenyo Dec 01 '16 at 22:00
  • 1
    "The thing is, the program crashes before the read method is running." What's the exception message? – DavidS Dec 01 '16 at 22:15
  • @DavidS My *guess* would be some "Invalid JSON" error caused by the missing comma after `amount: 1`, which is what I've been trying to tell Madmenyo, i.e. it's the bad JSON that needs to be fixed. – Andreas Dec 02 '16 at 00:26
  • @Andreas No it is not. Please try to have an open mind and step out of your tunnel vision. Like I am explaining, it not running the read method because I serialized it like a `ArrayList`, @Xoppa helped me out with that to get the "correct" format. For the final time "the library itself does not write proper Json". Not sure how you get the rep but you are really not helping out. I will show you in a edit how the plain old library functions and read/writes Json. The thing is, I want help with fixing my issue, not altering a "working" library so it writes proper Json. – Madmenyo Dec 02 '16 at 00:34
  • @DavidS My bad: `Can not set java.util.Set field com.buckriderstudio.towercrawler.Creature.Modifier.criteriaSet to java.util.ArrayList`. Like I'm saying, it somehow reads a `ArrayList` before it actually gets to my custom read method. I was sugested by @Xoppa to use the array for the format I wanted: http://stackoverflow.com/questions/40914151/serializetion-of-set – Madmenyo Dec 02 '16 at 00:34
  • @Andreas Made an edit. Have a look at it. Do you call that json library rubbish then explain exactly why and point me to a proper library if you want to help me out. Or tell me how to create proper Json with the current library because I have no idea how. Looked into PrettyPrintSettings and looked for other layout options without any luck. – Madmenyo Dec 02 '16 at 00:51
  • All I can say is: *That* is not JSON. It's may look similar to JSON, but it's not JSON. See **http://www.json.org/**, which has link to official JSON spec (ECMA-404). As for "proper library", I think any other JSON library would actually produce ... *you know* ... **JSON**. See "[How to parse JSON in Java](http://stackoverflow.com/q/2591098/5221149)". If your goal is JSON, then what you have generated there is not what you want. If you just want *some* text structure for your own use, you can keep that, if you want, but don't expect anybody else who support JSON to be able to read that. – Andreas Dec 02 '16 at 01:00
  • I don't know that library, but it seems you might be using the `minimal` output type: https://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/utils/JsonWriter.OutputType.html#enum.constant.detail. Change to `json`, if that's what you want to generate. – Andreas Dec 02 '16 at 01:10
  • @Andreas Now you just helped me with creating "proper" json. Yet the problem still remains. `"modifier": { "amount": 1, "criteriaSet": [ "VS_NATURE", "MULTIPLY" ]` – Madmenyo Dec 02 '16 at 02:40
  • I got it all to work with the `Jackson` library but it takes > 200ms to parse to file and back to object. It's not a big issue since it will load most of the stuff at runtime except saving and loading of course. But the LibGDX `json` parser does it between 10 and 15 ms which really is significant. – Madmenyo Dec 02 '16 at 09:21
  • 1000 times the `Jackson` parsing takes >2500ms where the other takes around 600ms. It's still a pretty big difference. I think I settle with Jackson for now since an extra 2 seconds loading time here and there won't matter that much. – Madmenyo Dec 02 '16 at 09:31
  • A lot of comments here about this being invalid Json. This is because Libgdx's Json class supports a modified version of Json that isn't as pedantic about requiring quotation marks around keys. – Tenfour04 Dec 02 '16 at 13:40
  • @Tenfour04 Yeah, we figured that out unfortunately it diluted the initial question, tried to point that out early on. – Madmenyo Dec 02 '16 at 13:42

1 Answers1

1

Although this isn't exactly answering my question, since it's still crashing before it gets to the read method, I have found a suitable work around using the Jackson library. I have figured out how to disregard default values by using the following annotation on the class to be serialized: @JsonInclude(JsonInclude.Include.NON_DEFAULT). This gets me the exact json output as what I was attempting with the build in json serializer. The only downside is the speed, Jackson is about 20 times slower on a single Object but looping that a 1000 times makes it "only" about 5 times slower.

For anyone who does not know, this is how you integrate Jackson with LibGDX:

In build add a dependency to the core project.

compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.0.1'

Starting parsing takes just a couple more lines.

    ObjectMapper mapper = new ObjectMapper();
    //Add pretty print indentation
    mapper.enable(SerializationFeature.INDENT_OUTPUT);

    //When serializing we have to wrap it in a try/catch signature
    try {
        mapper.writeValue(Gdx.files.local("creature.json").file(), creature);
    } catch (IOException e) {
        e.printStackTrace();
    }

    //To map it back to a object we do the same
    Creature jsonCreature = null;
    try {
        jsonCreature = mapper.readValue(Gdx.files.local("creature.json").readString(), Creature.class);
    } catch (IOException e) {
        e.printStackTrace();
    }

    //Jackson also has control over what you want to serialize
    mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
    //Or with annotation in front of the class
    @JsonIgnoreProperties({"nameOfProperty", "anotherProperty"})

This all at least gives me the same power as the build in json serializer and it serializes EnumSet right off the bat.

If someone knows how to de-serialize my write method in the initial question I will be glad to accept that as the answer.

Madmenyo
  • 8,389
  • 7
  • 52
  • 99