Search code examples
javajsonjacksongithub-apijsonserializer

Is there a built in mechanism within org.kohsuke.github to serialize GHRepository objects to JSON?


I'm using the Java Github API from org.kohsuke.github to retrieve a GHRepository object. I'd like to serialize this object to JSON. Is there a provided way of doing this within the org.kohsuke.github client? Possibly using Jackson?

Currently, when using jackson, I've had to create a custom pojo to avoid Jackson encountering null Map key's and values. I suspect this github library already has code to do this as I can see it serializing to JSON in GitHub.java but I'm not seeing a publicly accessible means of leveraging this. The code below is iterating over all my repos in all my orgs to get the JSON representation of the repo which will then be stored in a database.

// Iterate over all orgs that I can see on enterprise
github.listOrganizations().withPageSize(100).forEach( org -> {

    // Get all the repositories in an org
    Map<String, GHRepository> ghRepositoryMap = org.getRepositories();

    // Iterate over each repo object and serialize
    for (Map.Entry<String, GHRepository> entry :ghRepositoryMap.entrySet()  ) {

        // Serialize to JSON
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //Handles null fields ok
        String jsonStr = objectMapper.writeValueAsString(entry.getValue()); // <-- this fails due to null key

    }
}

The last line results in :

com.fasterxml.jackson.databind.JsonMappingException: Null key for a Map not allowed in JSON (use a converting NullKeySerializer?) (through reference chain: org.kohsuke.github.GHRepository["responseHeaderFields"]->java.util.Collections$UnmodifiableMap["null"])

From inspection I believe this isn't the only null key. I could probably customize the heck out of this by adding mixins to handle them but I'm looking for a built in way within the org.kohsuke.github library that makes this easier since it appears to be capable of doing this internally.


Solution

  • You should implement and register NullKeySerializer. See below example:

    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.databind.JsonSerializer;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    import com.fasterxml.jackson.databind.SerializerProvider;
    
    import java.io.IOException;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class JsonApp {
    
        public static void main(String[] args) throws Exception {
            Map<String, List<String>> map = new HashMap<>();
            map.put(null, Arrays.asList("A", "B", "C"));
            map.put("regular-key", Arrays.asList("X", "Y"));
    
            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);
            mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
            mapper.getSerializerProvider().setNullKeySerializer(new NullKeySerializer());
    
            System.out.println(mapper.writeValueAsString(map));
        }
    }
    
    class NullKeySerializer extends JsonSerializer<Object> {
        @Override
        public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeFieldName("null");
        }
    }
    

    Above code prints:

    {
      "null" : [ "A", "B", "C" ],
      "regular-key" : [ "X", "Y" ]
    }
    

    Also, you should not create ObjectMapper instance for each entry. You can create it once and use for all elements.