【问题标题】:Convert JSON to Java Object, how to parse BadgerFish convention with Jackson将 JSON 转换为 Java 对象,如何使用 Jackson 解析 BadgerFish 约定
【发布时间】:2017-12-12 14:49:48
【问题描述】:

使用 API 我收到这样的 JSON(现在保存到文件中):

[{
    "LEI": {
        "$": "549300Q82NZ9NYNMZT63"
    },
    "Entity": {
        "LegalName": {
            "$": "United Nerds in Collaboration of Random Nerdiness AB"
        },
        "LegalAddress": {
            "Line1": {
                "$": "BOX 155"
            },
            "City": {
                "$": "Alingsas"
            },
            "Region": {
                "$": "SE-O"
            },
            "Country": {
                "$": "SE"
            },
            "PostalCode": {
                "$": "44123"
            }
        },
        "HeadquartersAddress": {
            "Line1": {
                "$": "BOX 155"
            },
            "City": {
                "$": "Alingsas"
            },
            "Region": {
                "$": "SE-O"
            },
            "Country": {
                "$": "SE"
            },
            "PostalCode": {
                "$": "44123"
            }
        },
        "BusinessRegisterEntityID": {
            "@register": "SE001",
            "$": "5568557184"
        },
        "LegalJurisdiction": {
            "$": "SE"
        },
        "LegalForm": {
            "$": "PRIVATA AKTIEBOLAG"
        },
        "EntityStatus": {
            "$": "ACTIVE"
        }
    },
    "Registration": {
        "InitialRegistrationDate": {
            "$": "2016-06-23T01:48:45.025Z"
        },
        "LastUpdateDate": {
            "$": "2016-06-23T01:48:44.945Z"
        },
        "RegistrationStatus": {
            "$": "ISSUED"
        },
        "NextRenewalDate": {
            "$": "2017-06-21T06:32:03.821Z"
        },
        "ManagingLOU": {
            "$": "EVK05KS7XY1DEII3R011"
        },
        "ValidationSources": {
            "$": "PARTIALLY_CORROBORATED"
        }
    }
}]

我想从中获取 Java 对象。我已经从提供的 xsd 文件中创建了 Java 对象。我正在运行的代码是:

public static void toJava() {
    ObjectMapper mapper = new ObjectMapper();
    try {
        File json = new File("C:\\temp\\JSON.json");
        LEIRecordType[] type = mapper.readValue(json, LEIRecordType[].class);
    } catch (JsonEOFException ex) {
        ex.printStackTrace();
    } catch (JsonMappingException ex) {
        ex.printStackTrace();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

这会产生这些异常:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "LEI" (class   org.leiroc.data.schema.leidata._2014.LEIRecordType), not marked as ignorable (5 known properties: "lei", "registration", "entity", "nextVersion", "extension"])
 at [Source: (File); line: 3, column: 14] (through reference chain: java.lang.Object[][0]->org.leiroc.data.schema.leidata._2014.LEIRecordType["LEI"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:60)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:822)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1152)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1567)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1545)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:293)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:195)
at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:21)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2890)
at Test.JSONParser.toJava(JSONParser.java:38)
at Test.JSONParser.main(JSONParser.java:29)

LEIRecordType 如下所示:

package org.leiroc.data.schema.leidata._2014;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "LEIRecordType", propOrder = {"lei", "entity", "registration", "nextVersion", "extension"})
public class LEIRecordType {

    @XmlElement(name = "LEI", required = true)
    protected String lei;

    @XmlElement(name = "Entity", required = true)
    protected EntityType entity;

    @XmlElement(name = "Registration", required = true)
    protected RegistrationType registration;

    @XmlElement(name = "NextVersion")
    protected LEIRecordNextVersionType nextVersion;

    @XmlElement(name = "Extension")
    protected ExtensionType extension;

    public String getLEI() {
        return this.lei;
    }

    public void setLEI(String paramString) {
        this.lei = paramString;
    }

    public EntityType getEntity() {
        return this.entity;
    }

    public void setEntity(EntityType paramEntityType) {
        this.entity = paramEntityType;
    }

    public RegistrationType getRegistration() {
        return this.registration;
    }

    public void setRegistration(RegistrationType paramRegistrationType) {
        this.registration = paramRegistrationType;
    }

    public LEIRecordNextVersionType getNextVersion() {
        return this.nextVersion;
    }

    public void setNextVersion(LEIRecordNextVersionType paramLEIRecordNextVersionType) {
        this.nextVersion = paramLEIRecordNextVersionType;
    }

    public ExtensionType getExtension() {
        return this.extension;
    }

    public void setExtension(ExtensionType paramExtensionType) {
        this.extension = paramExtensionType;
    }
}

我知道问题在于杰克逊正在锁定一个名为 LEI 的 Java 对象,并带有一个名为“$”的变量。但是没有。组织帮助服务说:

““$”对象总是复制相应 XML 元素的简单内容(即不是属性、子节点等)。 在适用的情况下,“$”对象应始终输入为 JSON 字符串。”

但据我了解,这不是 JSON 标准。

我的问题是:有没有办法让杰克逊将其解析为 LEI = "549300Q82NZ9NYNMZT63" 等,而不是使用变量“$”来反对 LEI? 一天的大部分时间都被困在这个问题上。

@UPDATE 根据客户服务,这种 JSON 格式显然被称为“The BadgerFish 约定”。

【问题讨论】:

    标签: java json xsd jackson badgerfish


    【解决方案1】:

    由于 $ 对象始终是 String,您可以为处理 BadgerFish 包装器对象的字符串创建自定义反序列化器。

    此反序列化程序检查 String 值周围是否存在 BadgerFish 包装器对象并将其解包。正常的String 值像往常一样反序列化。

    public class BadgerFishDeserializer extends StdDeserializer<String> {
    
        private static final long serialVersionUID = 1L;
    
        private static final SerializedString BADGER_FISH_FIELD_NAME = new SerializedString("$");
    
        public BadgerFishDeserializer() {
            super(String.class);
        }
    
        @Override
        public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            // do we have a wrapper object?
            if (jp.isExpectedStartObjectToken()) {          
                // check if first field name is equal to '$'
                if (!jp.nextFieldName(BADGER_FISH_FIELD_NAME)) {
                    ctxt.reportInputMismatch(String.class, "Expected BadgerFish field name '$', but got '%s'", jp.getCurrentName());
                }
                jp.nextValue();  // proceed to the actual value
                String value = jp.getValueAsString();  // read value as string
                jp.nextToken();  // consume END_OBJECT of wrapper object
                return value;
            }
            // else: just return string value
            return jp.getValueAsString();
        }
    
    }
    

    最后在您的 Jackson ObjectMapper 实例上注册模块:

    SimpleModule module = new SimpleModule();
    module.addDeserializer(String.class, new BadgerFishDeserializer());
    mapper.registerModule(module);
    

    注意:如果您只想解开某些属性,您可以创建一个自定义注解并使用BeanDeserializerModifier 来检查注解,然后提供一个处理包装对象的反序列化程序。

    值得深思:

    • 创建注释
    • 修改反序列化器以始终期望包装对象(在纯字符串上失败)
    • 创建DeserializerModifier
    • ObjectMapper 上注册DeserializerModifier

    困难的部分:

    public class BadgerFishDeserializerModifier extends BeanDeserializerModifier {
    
        @Override
        public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
            Iterator<SettableBeanProperty> props = builder.getProperties();
            while (props.hasNext()) {
                SettableBeanProperty prop = props.next();
                if (prop.getAnnotation(MyAnnotation.class) != null) {   
                    builder.addOrReplaceProperty(prop.withValueDeserializer(new BadgerFishDeserializer()), true);
                }
            }
            return builder;
        }
    
    }
    

    【讨论】:

      【解决方案2】:

      这很有帮助!我最终不得不为 String 和 XMLGregorianCalendar 做一个特殊的反序列化器。但问题并不止于此。 BadgerFish 从生成的类中获取@XMLAttributes 并将它们设置为@value 而不是“value”。比如:

      "BusinessRegisterEntityID": {
              "@register": "SE001",
              "$": "5568557184"
          }
      

      那么他们还有什么方法可以将字段名称自定义回原始名称?目前我现在得到这个异常:

      Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "@register" (class leigen.BusinessRegisterEntityIDType), not marked as ignorable (2 known properties: "value", "register"])
      at [Source: (File); line: 44, column: 27] (through reference chain: java.lang.Object[][0]->leigen.LEIRecordType["Entity"]->leigen.EntityType["BusinessRegisterEntityID"]->leigen.BusinessRegisterEntityIDType["@register"])
      at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:60)
      at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:822)
      at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1152)
      at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1567)
      at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1545)
      at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:293)
      at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
      at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:136)
      at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:287)
      at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
      at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:136)
      at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:287)
      at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
      at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:195)
      at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:21)
      at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
      at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2890)
      at Test.JSONParser.toJava(JSONParser.java:47)
      at Test.JSONParser.main(JSONParser.java:28)
      

      我可以通过在配置中添加“忽略”(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)来绕过这个问题,就像我现在的当前代码一样:

          public static LEIRecordType[] toJava(File json) throws JsonParseException, JsonMappingException, IOException {
          ObjectMapper mapper = new ObjectMapper();
          SimpleModule module = new SimpleModule();
          module.addDeserializer(String.class, new BadgerFishStringDeserializer());
          module.addDeserializer(XMLGregorianCalendar.class, new BadgerFishXMLGregorianCalendarDeserializer());
          module.addDeserializer(NameType.class, new BadgerFishNameTypeDeserializer());
          mapper.registerModule(module);
          mapper.registerModule(new JaxbAnnotationModule()); // To be able to read JAXB annotations.
      
          mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
          return mapper.readValue(json, LEIRecordType[].class);
      }   
      

      但这只会将以@开头的值设置为空。还有其他方法可以解决这个问题吗?否则,我将不得不为我生成的所有类编写一个自定义反序列化器,大约是 25 个(下一个版本中总会有更多)。我已经为 NameType 做了一个,但对于其他示例,还需要更多。

      【讨论】:

      • 我希望你已经弄明白了,但以防万一:你可以在你的register字段上使用@JsonProperty("@register")
      【解决方案3】:

      我认为明确的解决方案是使用@JsonProperty("$"):

      ObjectMapper mapper = new ObjectMapper();
      mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
      List<LEIModel> leiList = Arrays.asList(mapper.readValue(response.toString(), LEIModel[].class));
      

      LEIModel 类:

      public class LEIModel {
         @JsonProperty("Entity")
         private Entity entity;
      
         public Entity getEntity() {
             return entity;
         }
      
         public void setEntity(Entity entity) {
             this.entity = entity;
         }
      }
      

      实体类:

      public class Entity {
         @JsonProperty("LegalName")
         private LegalName legalName;
      
         public LegalName getLegalName() {
             return legalName;
         }
      
         public void setLegalName(LegalName legalName) {
             this.legalName = legalName;
         }
      }
      

      LegalName 类:

      public class LegalName {
         @JsonProperty("$")
         private String value;
      
         public String getValue() {
             return value;
         }
      
         public void setValue(String value) {
             this.value = value;
         }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-03-25
        • 1970-01-01
        • 2017-08-31
        • 2017-02-04
        • 1970-01-01
        • 2018-07-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多