将自定义运行时逻辑应用于 SpringBoot @RestController 响应中的 JSON 序列化

发布于 2025-01-11 21:52:38 字数 1551 浏览 2 评论 0原文

我们正在构建一个应用程序,它可以从简化的对象模型中生成数百个独特的 JSON 有效负载结构,并且我们希望避免向 Java 代码库添加数百个 POJO(每个独特的有效负载结构 1 个)。

我们构建了一种方法来构建、解析有效负载结构的简单字符串规范并将其覆盖到 POJO,并遍历其树以匹配任何 @JsonProperty 字段。下面是一个示例规范:

String spec = """
  firstName
  lastName
  addresses.line1
  addresses.city
  children.firstName
  children.schedule.bedtime
"""

这将在运行时覆盖在 Person POJO 上,并且它将遍历并获取指定的字段和数组。尽管 PersonChildAddress POJO 中有更多字段,但此特定消息请求应该只填充我们指定的字段。其 JSON 输出应该是:

  {
    "firstName": "Mickey",
    "lastName": "Mouse",
    "addresses": [
      {
        "line1": "123 Disneyland Way",
        "city": "Anaheim"
      },
      {
        "line2": "345 Disneyworld Drive",
        "city": "Orlando"
      }
    ],
    "children": [
      "firstName": "Junior",
      "schedule": [
        "bedtime": "20:00"
      ]
    ]
  }

请耐心等待...似乎只有 1 个迪士尼角色有孩子 (Ariel)

目前,我们仅测试了我们的规范和 POJO 的遍历以查找位置该规范适用。但我们不知道如何将其连接到 JSON 序列化过程中。

我读过的关于 Jackson/JSON/Spring 序列化器和反序列化器的所有内容似乎只需要 1 个输入 - POJO。有没有一种方法可以使用使用 2 个输入(POJO + Spec)的自定义序列化器,其中 Spec 在运行时被识别(从而消除了为每个 Spec 创建“包装器”POJO 和/或序列化器的需要)?

为了让事情变得更具挑战性,我们希望简化我们的 @RestController 方法,以仅包含我们的新注释 @APIMessageSpec(

) 以及 >@ResponseBody 注释,然后让 Spring 调用我们的自定义序列化过程,传入 POJO 和我们的 @APIMessageSpec 的详细信息(或者可能让我们子类化 @ResponseBody > 到在其参数中参数化 Spec 信息,这样我们就不需要 2 个注释)?

提前致谢! 迈克尔

We are building an app that may produce hundreds of unique JSON payload structures from our streamlined object model and we want to avoid adding hundreds of POJOs to our Java codebase (1 per unique payload structure).

We built a way to build, parse and overlay a simple string spec of the payload structure to a POJO and to walk its tree to match any @JsonProperty fields. Here's an example spec:

String spec = """
  firstName
  lastName
  addresses.line1
  addresses.city
  children.firstName
  children.schedule.bedtime
"""

This would be overlaid on a Person POJO at runtime and it would traverse and get the fields and arrays specified. Though the Person, Child and Address POJOs have plenty more fields in them, this specific message request should only populate the ones we specified. The JSON output of this should be:

  {
    "firstName": "Mickey",
    "lastName": "Mouse",
    "addresses": [
      {
        "line1": "123 Disneyland Way",
        "city": "Anaheim"
      },
      {
        "line2": "345 Disneyworld Drive",
        "city": "Orlando"
      }
    ],
    "children": [
      "firstName": "Junior",
      "schedule": [
        "bedtime": "20:00"
      ]
    ]
  }

bear with me here...seems like only 1 Disney character has any children (Ariel)

Currently, we have only tested our Specs and the traversal of POJOs to find where the Spec applies. But we cannot figure out how to wire this into the JSON serialization process.

Everything I've read about Jackson/JSON/Spring serializers and deserializers seem to only take 1 input - the POJO. Is there a way to use custom serializers that use 2 inputs (POJO + Spec), where Spec is identified at runtime (thus eliminating the need to create a "wrapper" POJO per and/or serializer per Spec)?

To make things even more challenging, we want to simplify our @RestController methods to simply include our new annotation, @APIMessageSpec(<details go here>) alongside the @ResponseBody annotation and then have Spring invoke our custom serialization process, passing in the POJO and details of our @APIMessageSpec (or possibly for us to subclass @ResponseBody to parameterize the Spec info in its arguments, so that we don't need 2 annotations)?.

Thanks in advance!
Michael

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

撞了怀 2025-01-18 21:52:38

我在发布此内容后不久就找到了解决方案!

我们所有的数据/对象模型 POJO 在其祖先中共享一个超类,BaseModelEntity(我们在其中放置一些常见字段,如 UUID、上次更新用户/时间戳等)。

我们创建了 ModelJsonSerializer extends JsonSerializer< ;BaseModelEntity> 用于我们的自定义序列化规则。

我们选择了一个带有 @Autowired ObjectMapper objectMapper 的 bean 来添加以下方法:

    @PostConstruct
    public void init() {
        SimpleModule simpleModule = new SimpleModule("OurCustomModelJsonModule", new Version(1, 0, 0, null, null, null));
        simpleModule.addSerializer(BaseModelEntity.class, new ModelJsonSerializer());
        objectMapper.registerModule(simpleModule);
    }

我们有一个静态 APISpecRegistry 类,我们在其中加载所有规范 [这些规范都是我们自定义 的子类HashMap 中的 >APISpec 超类]。

在我们的 @RestController 方法上,或者我们应用程序中的任何地方,我们用 @JsonView() 注释返回类型

。为了处理我们的任何数据模型对象,它成功找到 ModelJsonSerializer 作为我们数据类型 BaseModelEntity 的正确对象。

在 ModelJsonSerializer 中,我们有以下方法:

@Override
    public void serialize(BaseModelEntity value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        Class c = serializers.getActiveView();
        if(c != null && APISpec.class.isAssignableFrom(c)) {
            List<Field> fields = ((APISpec)APISpecRegistry.getSpec(c).get()).getFields();
            serialize(value, gen, serializers, fields);
        } else {
            // default to out-of-the-box JSON serializers
        }
    }

注意:APISpec.getFields() 是我们将规范从字符串格式解析为要遍历的 POJO 字段层次结构的地方。

我们通过 serializers.getActiveView() 获取当前的 @JsonView,从该视图获取我们的 APISpec 类,并将其用作查找的关键 然后,在我们

的自定义 4 参数 serialize 方法中,我们将每个字段的 API 规则与相应的字段交叉引用字段值来自我们的 BaseModelEntity 实例,并忽略任何不在我们规范中的 BaseModelEntity 字段。

提示:在幕后,我们使用 Java 反射来查找带​​有与 中的字段匹配的 @JsonProperty 注释的 java.lang.reflect.Field。 API规范。因此,您可以想象,我们的 Person 对象中有以下字段:

@JsonProperty("firstName")
private String fName;

@JsonProperty("lastName")
private String lName;

@JsonProperty("addresses")
List<Address> addresses;

@JsonProperty("children")
List<Child> kiddos;

这太神奇了!

I figured out a solution not long after posting this!

All of our data/object model POJOs share a superclass in their ancestry, BaseModelEntity (where we put some common fields like UUID, last update user/timestamp, etc.)

We created ModelJsonSerializer extends JsonSerializer<BaseModelEntity> for our custom serialization rules.

We picked a bean with @Autowired ObjectMapper objectMapper to add the following method:

    @PostConstruct
    public void init() {
        SimpleModule simpleModule = new SimpleModule("OurCustomModelJsonModule", new Version(1, 0, 0, null, null, null));
        simpleModule.addSerializer(BaseModelEntity.class, new ModelJsonSerializer());
        objectMapper.registerModule(simpleModule);
    }

We have a static APISpecRegistry class where we load all of our specs [which all subclass our custom APISpec superclass] in a HashMap.

On our @RestController's methods, or anywhere in our application for that matter, we annotate the return type with @JsonView(<our_Spec_class>)

When Jackson identifies and starts to process any of our data model objects it successfully finds ModelJsonSerializer as the correct one for our data type BaseModelEntity.

In ModelJsonSerializer, we have the following method:

@Override
    public void serialize(BaseModelEntity value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        Class c = serializers.getActiveView();
        if(c != null && APISpec.class.isAssignableFrom(c)) {
            List<Field> fields = ((APISpec)APISpecRegistry.getSpec(c).get()).getFields();
            serialize(value, gen, serializers, fields);
        } else {
            // default to out-of-the-box JSON serializers
        }
    }

NOTE: APISpec.getFields() is where we parsed our spec from string format to a hierarchy of POJO fields to traverse.

We get the current @JsonView via serializers.getActiveView(), get our APISpec class from that view and use that as the key to lookup the APISpec object from our APISpecRegistry

Then, in our custom 4-argument serialize method, we cross-reference our API rules for each field with the corresponding field values from our BaseModelEntity instance and ignore any BaseModelEntity field that's not in our Spec.

TIP: Underneath the covers, we use Java reflection to find java.lang.reflect.Fields with a @JsonProperty annotation that matches up to the field in our APISpec. So, you can image, we have the following fields in our Person object:

@JsonProperty("firstName")
private String fName;

@JsonProperty("lastName")
private String lName;

@JsonProperty("addresses")
List<Address> addresses;

@JsonProperty("children")
List<Child> kiddos;

It's MAGIC!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文