Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UsingJsonInclude.Include.NON_DEFAULT together with JsonTypeInfo.defaultImpl should exclude the type property. #4522

Open
motlin opened this issue May 9, 2024 · 4 comments
Labels
to-evaluate Issue that has been received but not yet evaluated

Comments

@motlin
Copy link

motlin commented May 9, 2024

Is your feature request related to a problem? Please describe.

I'm trying to serialize a minimized Dropwizard configuration object, excluding any default values, in https://github.com/motlin/liftwizard/pull/2011/files.

The first thing I tried was setting Include.NON_DEFAULT globally, but it's well documented on the annotation itself that it does not work on objects globally, only primitives.

ObjectMapper nonDefaultObjectMapper = objectMapper.copy();
nonDefaultObjectMapper.setDefaultPropertyInclusion(Include.NON_DEFAULT);
nonDefaultObjectMapper.setSerializationInclusion(Include.NON_DEFAULT);

I came up with a decent workaround using mixins.

@JsonInclude(Include.NON_DEFAULT)
public class JsonIncludeNonDefaultMixIn
{
}

public class JsonIncludeNonDefaultMixInResolver
        implements MixInResolver
{
    @Override
    public Class<?> findMixInClassFor(Class<?> cls)
    {
        return JsonIncludeNonDefaultMixIn.class;
    }

    @Override
    public MixInResolver copy()
    {
        return this;
    }
}

ObjectMapper nonDefaultObjectMapper = objectMapper.copy();
nonDefaultObjectMapper.setMixInResolver(new JsonIncludeNonDefaultMixInResolver());
String configurationString = nonDefaultObjectMapper.writeValueAsString(configuration);
LOGGER.info("Dropwizard configuration (minimized):\n{}", configurationString);

This works fairly well and excludes most default values. Now when serializing a default configuration object, most of the remaining content comes from default type information set using JsonTypeInfo. For example:

  "logging": {
    "type": "default",
    "appenders": [
      {
        "type": "console"
      }
    ]
  },

Describe the solution you'd like

I'd like some way of excluding as many json fields as possible that get serialized on an object created with its default constructor, specifically the default type information from JsonTypeInfo's defaultImpl

Usage example

@JsonInclude(Include.NON_DEFAULT)
@JsonTypeInfo(use = Id.NAME, property = "type", defaultImpl = ExampleClass.class)
public interface ExampleInterface
{
}

@JsonTypeName("default")
@JsonInclude(Include.NON_DEFAULT)
public static class ExampleClass implements ExampleInterface
{
}

ExampleClass exampleClass = new ExampleClass();
String string = objectMapper.writeValueAsString(exampleClass);

String is:

{
  "type": "default"
}

I'd expect the empty object {} here.

Additional context

No response

@JooHyukKim
Copy link
Member

JooHyukKim commented May 9, 2024

From the description, it seems like the current implementation does not handle the concept of "default" across...

  • polymorphic type handling (@JsonTypeInfo), and
  • @JsonInclude

Question may start with... should it? The suggested feauture seems quite intuitive tho.

@cowtowncoder
Copy link
Member

This is unfortunately not doable: Type Ids (and Object Ids) are -- for the most part -- metadata, not data, and @JsonInclude and other mechanism only relate to data ("real" properties). So none of mechanisms defined will change inclusion of Type Ids; and I don't have any plans to change that.

But let's see if this particular problem could be solved using other mechanisms.

My first question here is that of why is polymorphic handling enabled? Is that necessary?
If no Type Ids are needed for serialization, there are (at least) 2 ways this could be done without changing POJO definitions:

  1. Using mix-ins, override @JsonTypeInfo with @JsonTypeInfo(use = Id.NONE) at level(s) necessary (base class)
  2. Overriding detection of @JsonTypeInfo in JacksonAnnotationIntrospector and blocking discovery (I forget specific method but it should be easy to find from class)

@motlin
Copy link
Author

motlin commented May 9, 2024

Some more context about what I'm trying to do. I'm working on Dropwizard configuration logging inside a library of Dropwizard add-ons that I wrote called Liftwizard.

Dropwizard ships with a way to deserialize config.yml into a Configuration object, including polymorphic configuration. The configuration objects include default values set up in constructors.

In order to know what configuration fields are even available to be changed, I find it helpful to serialize the config back to json and log it. For example, if I take a default Configuration object and serialize it...

Configuration plainConfiguration = new Configuration();
String plainConfigurationString = objectMapper.writeValueAsString(plainConfiguration);
LOGGER.info("Inferred Dropwizard configuration:\n{}", plainConfigurationString);

... I get a bunch of json.

{
  "logging": {
    "type": "default",
    "level": "INFO",
    "loggers": {},
    "appenders": [
      {
        "type": "console",
        "threshold": "ALL",
        "timeZone": "UTC",
        "queueSize": 256,
        "discardingThreshold": -1,
        "includeCallerData": false,
        "filterFactories": [],
        "target": "STDOUT"
      }
    ]
  },
  "metrics": {
    "frequency": "1 minute",
    "reporters": [],
    "reportOnStop": false
  },
  "server": {
    "type": "default",
    "maxThreads": 1024,
    "minThreads": 8,
...

... and a lot more, 143 lines total. It's sort of easier to work with this json than look up available properties in the configuration reference.

After making lots of edits to the config.yml, I'd like to see which parts I can now remove because they are defaults. I want to echo back a "minimized" configuration object.

As a test, I can try to serialize new Configuration() again and see how small the MixIn workaround can get to the empty object {}.

ObjectMapper nonDefaultObjectMapper = objectMapper.copy();
nonDefaultObjectMapper.setMixInResolver(new JsonIncludeNonDefaultMixInResolver());
String configurationString = nonDefaultObjectMapper.writeValueAsString(new Configuration());
LOGGER.info("Dropwizard configuration (minimized):\n{}", configurationString);

The resulting json is much shorter.

{
  "logging": {
    "type": "default",
    "appenders": [
      {
        "type": "console"
      }
    ]
  },
  "server": {
    "type": "default",
    "applicationConnectors": [
      {
        "type": "http"
      }
    ],
    "adminConnectors": [
      {
        "type": "http",
        "port": 8081
      }
    ],
    "serverPush": {},
    "requestLog": {
      "type": "logback-access",
      "appenders": [
        {
          "type": "console"
        }
      ]
    },
    "gzip": {}
  },
  "metrics": {},
  "admin": {
    "healthChecks": {},
    "tasks": {}
  }
}

There are a few empty objects left, and I think that's because the Java types don't implement equals(). Besides that, everything remaining is polymorphic type information.

@cowtowncoder
Copy link
Member

cowtowncoder commented May 10, 2024

One thing that could be conceptually acceptable (and useful) would be to support rule of "do not write Type Id if it is the same as defaultImpl. But it is quite literally easier said than done.

But if anyone could figure it out (I don't have time to dig deep here but I always try to help those that do!), that could help here I think?

And I have feeling there might actually be an issue filed for this.

EDIT: indeed there is... #644 . Almost 10 years old, vintage issue. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
to-evaluate Issue that has been received but not yet evaluated
Projects
None yet
Development

No branches or pull requests

3 participants