【问题标题】:How to serialize JSON with array field to object with String field?如何将带有数组字段的 JSON 序列化为带有字符串字段的对象?
【发布时间】:2017-01-24 09:48:37
【问题描述】:

我有一个像

这样的 JSON 对象
{
  "id" : "1",
  "children" : ["2","3"]
}

我有一个类似的 Java 对象(构造函数、getter 和 setter 被省略):

public class Entity {
    public String id;
    public String children;
}

我希望通过这段代码使用 Jackson 将此 JSON 反序列化为我的 Java 对象:

Entity entity = mapper.readValue(json, Entity.class);

但是得到以下错误:

Can not deserialize instance of java.lang.String out of START_ARRAY token

如何在不更改 children 字段类型的情况下解决它?

children 字段应具有以下值:["2","3"]

【问题讨论】:

  • 你能把children的类型改成List<String>吗?
  • 如果你不想改变类型,你想如何存储 "children":["2","3"] ?你只想要第一个值?
  • 我想要类似 "["2","3"]" 的东西(引号可以带反斜杠)
  • 所以你需要发送像 "children":"[2,3]" 这样的 json,如果可以的话,否则你需要创建和注册自定义映射器
  • 我不认为你可以用标准映射器做到这一点,编写自定义映射器

标签: java json serialization jackson


【解决方案1】:

创建自定义反序列化程序

创建自定义反序列化程序以获取原始 JSON 值。您可以根据需要选择以下实现之一:

  1. 它将为您提供 JSON原样,即保留所有空格和制表符:
public class RawJsonDeserializer extends JsonDeserializer<String> {

    @Override
    public String deserialize(JsonParser jp, DeserializationContext ctxt)
           throws IOException, JsonProcessingException {

        long begin = jp.getCurrentLocation().getCharOffset();
        jp.skipChildren();
        long end = jp.getCurrentLocation().getCharOffset();

        String json = jp.getCurrentLocation().getSourceRef().toString();
        return json.substring((int) begin - 1, (int) end);
    }
}
  1. 它将为您提供没有额外空格和制表符的 JSON:
public class RawJsonDeserializer extends JsonDeserializer<String> {

    @Override
    public String deserialize(JsonParser jp, DeserializationContext ctxt)
           throws IOException {

        JsonNode node = jp.getCodec().readTree(jp);
        ObjectMapper mapper = (ObjectMapper) jp.getCodec();
        return mapper.writeValueAsString(node);
    }
}

注释你的类以使用上面定义的反序列化器

通过使用引用上面定义的反序列化器的@JsonDeserialize 注释children 属性来更改Entity 类:

public class Entity {

    public String id;

    @JsonDeserialize(using = RawJsonDeserializer.class)
    public String children;
}

解析 JSON

然后使用 ObjectMapper 解析 JSON,Jackson 将使用您的自定义反序列化器:

String json = "{\"id\":\"1\",\"children\":[\"2\",\"3\"]}";

ObjectMapper mapper = new ObjectMapper();
Entity entity = mapper.readValue(json, Entity.class);

children 属性的值将是["2","3"]


更多详情,请查看question

【讨论】:

    【解决方案2】:

    将您的对象编组为 JSON 格式。
    然后从 JSON 文件中解组

    public interface MarshallingSupport {
    public String marshal(Object object);
    public <T> T unmarshal(String s, Class<T> t);
    }
    
    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import java.io.IOException;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    public class JacksonJSONMarshallingSupport implements MarshallingSupport {
    
    private final ObjectMapper mapper;
    
       public JacksonJSONMarshallingSupport(ObjectMapper mapper) {
           this.mapper = mapper;
           this.mapper.getFactory().configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
       }
    
        @Override
        public String marshal(Object object) {
           try {
                return mapper.writeValueAsString(object);
               } catch (JsonProcessingException ex) {
                   throw new RuntimeException(ex);
               }
       }
    
       @Override
       public <T> T unmarshal(String s, Class<T> t) {
           try {
                 T newObj = mapper.readValue(s, t);
                 return newObj;
               } catch (IOException ex) {
                   throw new RuntimeException(ex);
               }
       }
    }
    

    【讨论】:

      【解决方案3】:

      接受@Cassio 的回答,如果您不想或无法注释您的 Entity 类,只需添加一些配置即可。

      首先创建一个抽象类 [for method annotation purpose you can create an interface, but in this case we will annotate a bean property so we create an abstract class, and if you also want to annotate a method in this abstract class you have to declare that method as abstract],它类似于 Jackson 配置的 mime bean:

      public abstract class EntityMixIn {
      
          @JsonDeserialize(using = RawJsonDeserializer.class)
          public String children;
      
      }
      

      现在,您必须告诉您的映射器采用这个 mixin 类并像原来的 Entity 类一样工作,只是为了这个配置目的:

      mapper.addMixIn(Entity.class, EntityMixIn.class);
      

      【讨论】:

      • Jackson 混合注释是一个很好的资源,当你不能修改你的类时。然而说混入界面会像原来的Entity 那样表现是不准确的。您可以将混合注解视为一种在运行时添加更多注解以扩充静态定义的注解的面向方面的方式
      • 是的!你是对的,但这只是为了举例,这就是为什么我之前说它会像一个 mime bean,但我会更新这个确认。谢谢。
      • 我赞成你的回答。我认为这是对我提供的解决方案的一个很好的贡献:)
      • 谢谢@Cassio。我还阐明了抽象类的用途。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-15
      • 2020-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多