使用杰克逊数据绑定跳过嵌套字段?

Posted

技术标签:

【中文标题】使用杰克逊数据绑定跳过嵌套字段?【英文标题】:Skipping nested field with Jackson data binding? 【发布时间】:2015-10-23 11:47:22 【问题描述】:

我必须如何使用 Jackson 将以下 json sn-p 绑定(双向)到下面的 Java 对象?

我希望 Json newsletter 节点的键/值对最终出现在 newsletter 字段的 Map 中。 绑定 this 的 Jackson 配置(注释和其他)必须是什么? 抱歉,我不知道怎么做 :((使用最新版本的 Jackson)。

下面的 Json 格式是固定的,我是从第三方 rest 服务收到的(我无法更改)。我可以更改 java 代码,但更喜欢下面的设置。

JSON:


    "newsletter": 
        "key1": "value1",
        "key2": "value2",
        "key3": "value3"
    

Java 对象:

class Preferences 
    private NewsLetter newsLetter;
    // getter/setter for field newsLetter.


class NewsLettter 
    private Map<String, String> properties;
    // getter/setter for field properties.

【问题讨论】:

“下面的Json格式是固定的,我是从第三方rest服务收到的”第三方服务生成无效的JSON?顺便说一句,您需要保留NewsLettter 课程吗? 我喜欢 NewsLetter 类,但如果你有一个好主意,可以删除/修改它。打我;) 【参考方案1】:

您可以简单地使用@JsonAnySetter 进行反序列化

可用于定义非静态、双参数方法(属性的第一个参数名称,要设置的第二个值)的标记注释,用作从 JSON 中找到的所有其他无法识别的属性的“后备”处理程序内容。

还有@JsonAnyGetter 用于序列化。

可用于将非静态、无参数方法或成员字段定义为与JsonAnySetter 方法相反的标记注释;基本上像 getter 一样使用,但是返回的 Map 的内容(类型必须是 Map)被序列化,就好像它们是包含带有此注释的方法/字段的 bean 的实际属性

public class Newsletter 

    private final Map<String, String> properties = new HashMap<>();

    @JsonAnySetter
    public void addProperty(String name, String value) 
        properties.put(name, value);
    

    @JsonAnyGetter
    public Map<String, String> getProperties() 
        return properties;
    

这应该适用于您的用例。

测试

public class Main 

    public static void main(String[] args) throws Exception 
        String json
                = "\n"
                + "    \"newsletter\": \n"
                + "        \"key1\": \"value1\",\n"
                + "        \"key2\": \"value2\",\n"
                + "        \"key3\": \"value3\"\n"
                + "    \n"
                + "";

        ObjectMapper mapper = new ObjectMapper();
        Preferences prefs = mapper.readValue(json, Preferences.class);
        Map<String, String> properties = prefs.getNewsletter().getProperties();
        for (Map.Entry<String, String> prop: properties.entrySet()) 
            System.out.println(prop.getKey() + ":" + prop.getValue());
        
    

另请参阅:

Jackson tips: using @JsonAnyGetter/@JsonAnySetter to create "dyna beans"

【讨论】:

谢谢。我只是完全按照您上面的建议使用它,并且反序列化工作正常:实例包含带有以下内容的映射:“key1=value1, key2=value2, key3=value3” 但是,将其序列化回 JSON 会导致:“” newsLetters":"emptyProperties":false,"key1":"value1","key2":"value2","key3":"value3"" 我不知道这个额外的键/值对“emptyProperties”在哪里/false”来自(不是我的)。任何想法 ?也许来自@JsonTypeInfo?...我使用 ObjectMapper.writeValueAsString(obj)... 是的,我不知道。我刚刚使用上面的示例进行了测试,使用 S.o.p(mapper.writeValueAsString(prefs)) 并且我没有看到额外的属性。 Newsletter 是具有该属性的其他类的子类吗? 感谢您的快速回复。您如何向映射器指定要使用的 NewsLetter 实现? NewsLetterDefault 实现从 MapProperties 扩展:具有“私有 Map 属性”字段的类和用于添加/删除属性的所有类型的 setter/getter。 NewsLetter 接口从相应的“HasProperties”接口扩展而来。我猜这会导致添加 ghost 属性 ;),但不知道怎么做? 您是否尝试将@JsonIgnore 添加到问题属性?还是我误解了你在说什么?没有财产吗?这就是你所说的“幽灵”吗? 如果@JsonValue 有效,我认为创建mixin 没有任何好处。我会同意的【参考方案2】:

如果你能得到一个合适的 JSON,像这样:


    "newsletter": 
        "key1": "value1",
        "key2": "value2",
        "key3": "value3"
    

注意, 是分隔符而不是;

那么您可以使用以下类来保存您的 JSON 数据:

class Preferences 
    @JsonProperty("newsletter")
    private Map<String, String> newsLetter;

    @Override
    public String toString() 
        return "Preferences" + "newsLetter=" + newsLetter + '';
    

在这里,您不再使用 NewsLetter 类,因为您需要一个嵌套键,就像 Roman C 在他的回答中所说的那样。只需将Map 字段直接添加到您的Preferences 类中即可。

您可以像这样读取您的 JSON:

public static void main(String[] args) throws Exception 
    ObjectMapper om = new ObjectMapper();
    System.out.println(om.readValue(new File("example.json"), Preferences.class));

它会打印:

偏好newsLetter=key1=value1, key2=value2, key3=value3

【讨论】:

感谢您的解决方案。抱歉,分隔符错误,刚刚更正。几天前我试过这个,效果很好。但我喜欢在他自己的类中抽象 Newsletter 的东西,这样我就可以添加其他东西,比如查找器和其他操作方法。使用您的解决方案,NewsLetter 类的类 Preferences 和内部变得依赖(紧密耦合)。 如果您无法更改 JSON,那么我所知道的唯一方法仍然是使用 Jackson ist 删除地图并为 NewsLetter 类中的每个键添加一个字段。由于这很愚蠢,我认为您需要删除 Jackson :(。但我可以建议一个不同的库。 我不知道 Newsletter 中的键/值对,它们是动态的,不是固定的,每时每刻都在变化。【参考方案3】:

以下是 JSON 到 Java 转换的工作原理

第一步是创建保存 JSON 数据的 Java 类。你已经有了一个。看一下json。您创建一个 Preferences 对象来保存 整个json。 json 包含一个 newsletter 元素。你 在Preferences 对象中创建Newsletter 类型的Java 对象。 创建org.codehaus.jackson.map.ObjectMapper 类的实例。这是课 将 JSON 映射到 Java 对象。

 ObjectMapper mapper = new ObjectMapper();

然后使用ObjectMapperreadValue方法读取。此方法有多个版本,您可以使用采用 String。但是,有些方法可以从 URLFileInputStreamStringByteArray 读取。

ObjectMapper 缓存序列化器和反序列化器,因此最好将ObjectMapper 实例用于多个 转化 如果您有 String,则将其传递给 Jackson 以将其转换为 Java 对象。

String str = "
     \"newsletter\":
      
        \"properties\": 
         \"key1\" : \"value1\",
         \"key2\" : \"value2\",
         \"key3\" : \"value3\"
      
     
   ";

//read json
Preferences preferences = mapper.readValue(str, Preferences.class);
//write json
mapper.writeValue(str, preferences);

您可以使用将 json 转义为 java 字符串的工具:http://www.freeformatter.com/java-dotnet-escape.html

此工具产生以下结果

 \"newsletter\":\r\n          \r\n            \"properties\": \r\n             \"key1\" : \"value1\",\r\n             \"key2\" : \"value2\",\r\n             \"key3\" : \"value3\"\r\n          \r\n         \r\n        

【讨论】:

如前所述,它是一个“sn-p”,是更大 json 的一部分。 感谢您提供详细信息。我知道他们。但是如何在不更改 Json 的情况下解决我的问题,因为我无法更改它。就像汤姆在他的回答中提到的那样,我更喜欢当前的抽象关系 Preference -> Newsletter。 如果你知道,为什么要问它?如果类没有必需的属性,则从 json 反序列化将不起作用,并且 json 应该具有反序列化器工作的有效格式。 我想为这个答案投票,因为当我在寻找解决方案时,它对我有帮助 【参考方案4】:

我想我成功了。 我用

@JsonValue 创建时事通讯的 JSON 部分。 @JsonCreator 使用地图创建 NewsLetter 实例 包含键/值对。 @JsonTypeInfo 指示使用哪个实现来创建 NewsLetter 实例。

使用上面的 json sn-p ,测试运行良好,双向。 在代码中(Preferences 类与原始问题中列出的相同):

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.EXISTING_PROPERTY, defaultImpl = NewsLetterPreferencesDefault.class)
public interface NewsLetterPreferences  


public final class NewsLetterPreferencesDefault 
    private Map<String, String> properties;

    @JsonCreator
    public NewsLetterPreferencesDefault(final Map<String, String> properties) 
        this.properties = properties.
    

    @JsonValue
    public Map<String, String> getAllProperties() 
        return this.properties;
    

有办法吗?我以前从未使用过@JsonTypeInfo,所以我希望它是正确的。我会用谷歌搜索更多的例子。感谢所有反馈。

【讨论】:

虽然这看起来没问题,但我认为这是矫枉过正。还有更简单的方法。 对不起。当我看到peeskillet的答案时,我将发布答案。这是最好的方法。

以上是关于使用杰克逊数据绑定跳过嵌套字段?的主要内容,如果未能解决你的问题,请参考以下文章

杰克逊数据绑定枚举不区分大小写

龙目岛没有创建默认构造函数导致杰克逊数据绑定失败[重复]

GridView 与嵌套类的属性绑定

Visibility = IsCollapsed 是不是跳过数据绑定部分?

在使用数据绑定时如何设置嵌套的回收器视图适配器?

WinForm 绑定到嵌套对象上的属性