1

How can I generate java record objects for java schemas with the anyOf field?

I have the following schemas:

animals.json

{
    "type": "object",
    "properties": {
        "animals": {
            "type": "array",
            "items": {
                "anyOf": [
                    {"$ref": "mammale.json"},
                    {"$ref": "bird.json"}
                ],
                "minContains": 1,
                "uniqueItems": true
            }
        }
    }
}

mammal.json

{
    "type": "object",
    "properties": {
        "mammalType": {"type": "string"},
        "furColor": {"type": "string"}
    }
}

bird.json

{
    "type": "string"
}

I want to output a json that looks like the following:

{
    "animals": [
        {
            "mammalType": "dog",
            "furColour": "brown"
        },
        "someBirdsName"
    ]
}

Is there a recommended way to represent the 'anyOf' structure in the java records (preferably with Jackson annotation)?

I found a solution when using POJOs, where they use an interface: Json schema with anyOf fields to POJO

This looks like a good solution but how do I us an arbitrary string that doesnt implements the interface?

Djoust
  • 13
  • 3
  • We have 4 generators for Java listed at https://json-schema.org/implementations.html#code-generation. Maybe one of them will work for you. – gregsdennis Mar 30 '23 at 18:48
  • 1
    I think interfaces is the right way to handle `anyOf` for a strictly-typed language like .Net or Java. I think the lib is doing it right. – gregsdennis Mar 30 '23 at 18:49
  • Is the goal to write the json or read it. Writing is easy because Java knows what animal record types are in the main object - I can give you an answer if this is the case. Reading, however, requires the animal json to carry some indication of the animal type because, otherwise, there is no way for the deserialiser to know which kind of animal is being read. If animalType can be added to each of the animals, then I can provide a solution. – John Williams Mar 30 '23 at 18:58
  • 1
    Incorrect: Java records can indeed implement interfaces. A record cannot *extend* another class because it already extends the abstract class `java.lang.Record`. To quote [JEP 395](https://openjdk.org/jeps/395): A record class can implement interfaces. A record class cannot specify a superclass since that would mean inherited state, beyond the state described in the header. A record class can, however, freely specify superinterfaces and declare instance methods to implement them. … – Basil Bourque Mar 30 '23 at 20:14
  • @JohnWilliams the goal is to generate json from my java record, i made a slide mistake in coming up with an example, in the real world the second anyOf type is just a (array of) string. – Djoust Mar 31 '23 at 10:28
  • @BasilBourque Thanks! Records can implement an interface. Why it didnt work for me was that the record that needs to implement the interface was a nested record. This is an easy fix. So than i could use the annotation examples in the linked post. But what if i want to use a json opbject and a string in the anyOf statement? – Djoust Mar 31 '23 at 10:29

1 Answers1

0

To produce json like the following:

{
  "animals" : [ {
    "mammalType" : "dog",
    "furColour" : "brown"
  }, 
    "someString"
  ]
}

Which corresponds to schema like this:

{
    "type": "object",
    "properties": {
        "animals": {
            "type": "array",
            "items": {
                "anyOf": [
                    {"$ref": "mammale.json"},
                    {"type": "String"}
                ],
                "minContains": 1,
                "uniqueItems": true
            }
        }
    }
}

Then the following will do it:

public record ResponseServiceAlgolia(Object[] animals) {}

public record Mammal(String mammalType, String furColour) {}

public static void main(String[] args) throws JsonProcessingException {
    
    Mammal mammal = new Mammal("dog", "brown");
    
    Object[] animals = new Object[] {mammal, "someString"};
    ResponseServiceAlgolia responseServiceAlgolia = new ResponseServiceAlgolia(animals);
    
    ObjectMapper objectMapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
    
    var json = objectMapper.writeValueAsString(responseServiceAlgolia);
    System.out.println(json);

}

Output:

{
  "animals" : [ {
    "mammalType" : "dog",
    "furColour" : "brown"
  }, "someString" ]
}
John Williams
  • 4,252
  • 2
  • 9
  • 18
  • Thank you, this is some what the solution i'm looking for... I now see that i had to state my initial question more clearly and that I need to put the desired output in the question, I'll update it. But basically the output I need to generate looks like this: ``` json { "animals" : [ { "mammalType" : "dog", "furColour" : "brown" }, "someString"] } ``` Hope you can help me with this? – Djoust Mar 31 '23 at 14:46
  • 1
    Oke, im an idiot, the fist example can clearly do this... – Djoust Mar 31 '23 at 15:15
  • Updated answer anyway – John Williams Mar 31 '23 at 15:25