如何将Map属性转换为JSON字段

huangapple go评论65阅读模式
英文:

How to convert Map attribute into JSON fields

问题

我需要在数据库中存储一些带有自定义(动态创建的)列的实体。为了实现这个目标,我在表中创建了带有动态列的附加列,列的类型是JSONB(在这个项目中我使用的是PostgreSQL)。在应用程序端,我使用Vlad Mihalcea的Hibernate Types库将这些附加列存储在Map中。为了从我的应用程序返回Web资源所需的内容,我需要将表映射到Java的POJO,然后将其发送出去。但是在将我的POJO转换为JSON时遇到了问题。Web要求一个不带嵌套属性的简单JSON,例如:

{
  "name": "name",
  "1": 100,
  "2": true
}

在上面的JSON中,1和2是自定义列(是的,我们决定为此目的使用ID)。但是,如果我将我的类与Map作为类属性进行映射,响应将包含嵌套结构:

{
  "name": "name",
  "customAttributes": {
     "1": 100,
     "2": "test"
  }
}

实际上,将类强制转换为Map,移除键“customAttributes”,然后将该映射的值放回去并不是什么大问题。我的解决方案如下所示:

public Map<String, Object> mapToResource(Account account) {
    var accountMap = objectMapper.convertValue(account, new TypeReference<Map<String, Object>>() {});

    if (isClassHasCustomAttributes(account.getClass())) {
        var customAttributesMap = account.getCustomAttributes();
        accountMap.remove("customAttributes");
        accountMap.putAll(customAttributesMap);
    }

    return accountMap;
}

private boolean isClassHasCustomAttributes(Class<?> classUnderCheck) {
    return Arrays.stream(classUnderCheck.getDeclaredFields()).anyMatch(field -> field.getName().equals("customAttributes"));
}

我添加了一个检查名为customAttributes的属性的检查,因为并非所有实体都包含Map作为属性,而且我在应用程序中为所有表使用了泛型。我不确定在控制器层将所有类强制转换为Map<String, Object>是否是解决这种情况的好方法。

总之,我想知道是否有更好的方法在不将类强制转换为Map<String, Object>的情况下返回所请求的实体?

英文:

I need to store some entities in database with custom (dynamic created) columns. For reach that goal I created additional column in tables with dynamic columns and type of column is JSONB (I work with postgresql on this project). On application side I'm storing these additional columns in Map using hibernate types library from Vlad Mihalcea. To return required by web resources from my application I need map table onto java's pojo and after that just send it. But I faced with problem when converting my pojo to json. Web requires a simple JSON without nested propetries, for example:

{
  &quot;name&quot;: &quot;name&quot;,
  &quot;1&quot;: 100,
  &quot;2&quot;: true
}

On the JSON above 1 and 2 are custom columns (yeah, we decided to use ids for that goal). But, if I'm mapping my class with Map as class property, response will contain nested structure:

{
  &quot;name&quot;: &quot;name&quot;,
  &quot;customAttributes&quot;: {
     &quot;1&quot;: 100,
     &quot;2&quot;: &quot;test&quot;
  }
}

Actually, it's not a big deal to cast class into the Map, remove key "customAttributes" and put value of this map back. My solution is in code below:

public Map&lt;String, Object&gt; mapToResource(Account account) {
    var accountMap = objectMapper.convertValue(account, new TypeReference&lt;Map&lt;String, Object&gt;&gt;() {});

    if (isClassHasCustomAttributes(account.getClass())) {
        var customAttributesMap = account.getCustomAttributes();
        accountMap.remove(&quot;customAttributes&quot;);
        accountMap.putAll(customAttributesMap);
    }

    return accountMap;
}

private boolean isClassHasCustomAttributes(Class&lt;?&gt; classUnderCheck) {
    return Arrays.stream(classUnderCheck.getDeclaredFields()).anyMatch(field -&gt; field.getName().equals(&quot;customAttributes&quot;));
}

I added check for property named customAttributes because not all entities contain Map as property and I'm using generics for all my tables in app. I'm not sure that casting all my classes into Map on controller layer is a good solution for this sutiation.
In summary, I need to know - is there a better way to return requested entity without casting classes into Map<String, Object> or no?

答案1

得分: 1

实际上,解决方案非常接近(或者也许并不接近,无关紧要)。在Jackson中有一个注解 @JsonAnyGetter。您需要做的只是(如果您遇到与我类似的问题)对您的Map属性进行注释。当对象从POJO序列化为JSON时,Jackson将会将Map中的所有键值对添加到JSON中。

以下是示例

public class Account {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;
  private String name;
  private Boolean isBlocked;
  private Integer childCount;
  private LocalDateTime createdAt;

  @Type(type = "jsonb")
  @Column(columnDefinition = "jsonb")
  private Map<String, Object> customAttributes;

  @JsonAnyGetter
  public Map<String, Object> getCustomAttributes() {
      return customAttributes;
  }
}

Jackson注解的有用链接

英文:

Actually, solution was so close (or maybe not, doesn't matter). There is annotation @JsonAnyGetter in Jackson. All you need to do (if you face with similar problem as I did) it annotate your Map property. Jackson will add all key-value from Map into JSON when object will serialize from pojo to json.

Here the example:

public class Account {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;
  private String name;
  private Boolean isBlocked;
  private Integer childCount;
  private LocalDateTime createdAt;

  @Type(type = &quot;jsonb&quot;)
  @Column(columnDefinition = &quot;jsonb&quot;)
  private Map&lt;String, Object&gt; customAttributes;

  @JsonAnyGetter
  public Map&lt;String, Object&gt; getCustomAttributes() {
      return customAttributes;
  }
}

Useful link on Jackson's annotations

huangapple
  • 本文由 发表于 2020年9月15日 23:42:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/63905379.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定