question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

JsonParser should provide a method that returns current target value type

See original GitHub issue

Is your feature request related to a problem? Please describe. we want deserialize generic class based on target value type at customerized JsonDeserializer.

//  generic class
@Data
@JsonSerialize(using = CustomSerializer.class)
@JsonDeserialize(using = CustomDeserializer.class)
public class A<U extends BaseU, V extends BaseV> {
  // all U have AnnotationForU as json field key.
  private U u;
  // all V have AnnotationForV as json field key.
  private V v;
}

@AnnotationForU(value = "NameU")
public class U extends BaseU {
  ...
}

@AnnotationForV(value = "NameV")
public class V extends BaseV {
  ...
}

// customerized serializer
public class CustomSerializer extends JsonSerializer<Object> {
  @Override
  public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
    if (value instanceof A) {
      final A<?, ?> a = (A<?, ?>) value;
      gen.writeStartObject();
      //  get AnnotationForU value as JSON field key of u.
      gen.writeObjectField(a.u.getClass().getAnnotation(AnnotationForU.class).value(), a.u);
      //  get AnnotationForV value as JSON field key of v.
      gen.writeObjectField(a.v.getClass().getAnnotation(AnnotationForV.class).value(), a.v);
      gen.writeEndObject();
    }
  }
}

// customerized deserializer
public class CustomDeserializer extends JsonDeserializer<Object> {
  @Override
  public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
    // we want get current target value type from JsonParser.
    JavaType toValueType = p.getToValueType();
    if (toValueType instanceof TypeBase) {
      // get type bindings.
      TypeBindings bindings = ((TypeBase) toValueType).getBindings();
      // map json field key -> generic name.
      Map<String, String> boundNameMap = new HashMap<>();
      // map json field key -> class.
      Map<String, Class<?>> clzMap = new HashMap<>();
      for (int i = 0; i < bindings.size(); i++) {
        switch (bindings.getBoundName(i)) {
          case "U":
            String uFieldName = bindings.getBoundType(i).getRawClass().getAnnotation(AnnotationForU.class).value();
            boundNameMap.put(uFieldName, "U");          
            clzMap.put(uFieldName, bindings.getBoundType(i).getRawClass());
            break;
          case "V":
            String vFieldName = bindings.getBoundType(i).getRawClass().getAnnotation(AnnotationForV.class).value();
            boundNameMap.put(vFieldName, "V");
            clzMap.put(vFieldName, bindings.getBoundType(i).getRawClass());          
            break;
          default:
            throw new RuntimeException("unknown bound name: " + bindings.getBoundName(i));
        }
      }
      // deserializing A
      A<Object, Object> a = new A<>();
      ObjectMapper om = (ObjectMapper) p.getCodec();
      ObjectNode objectNode = (ObjectNode) om.readTree(p);
      Iterator<Entry<String, JsonNode>> it = objectNode.fields();
      while (it.hasNext()) {
        Entry<String, JsonNode> entry = it.next();
        Class<?> clz = clzMap.get(entry.getKey());
        if (clz == null) {
          throw new RuntimeException("unknown json field name: " + entry.getKey());
        }
        Object value = om.convertValue((ObjectNode) entry.getValue(), clz);
        String boundName = boundNameMap.get(entry.getKey());
        if ("U".equals(boundName)) {
          a.setU(value);
        } else {
          a.setV(value);
        }
      }
      return a;
    }
    throw new RuntimeException("unknown toValueType: " + toValueType);
  }
}

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:10 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
zrlwcommented, Aug 27, 2022

as tatu said, you should be using createContextual for this use case

thanks, we got it.

    @JsonSerialize(using = MyType.CustomSerializer.class)
    @JsonDeserialize(using = MyType.CustomDeserializer.class)
    public static class MyType<T, K> {
        private String title;
        private T t;
        private K k;
        
        public static class CustomSerializer extends JsonSerializer<Object> {
            /** bean -> map */
            @Override
            public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                if (value instanceof MyType) {
                    final MyType<?, ?> my = (MyType<?, ?>) value;
                    gen.writeStartObject();
                    gen.writeStringField("title", my.title);                    
                    // using serializers.defaultSerializeField instead of gen.writeObjectField to avoid losing current active context.
                    serializers.defaultSerializeField(my.t.getClass().getCanonicalName(), my.t, gen);
                    serializers.defaultSerializeField(my.k.getClass().getAnnotation(JsonRootName.class).value(), my.k, gen);                    
                    gen.writeEndObject();
                }
            }
        }
        
        public static class CustomDeserializer extends JsonDeserializer<MyType<?, ?>> implements ContextualDeserializer {            

            /** map json field key -> generic type name */
            private Map<String, String> boundNameMap;            

            /** map json field key -> class */
            private Map<String, Class<?>> clzMap; 
           
            public CustomDeserializer() {
                this.boundNameMap = null;
                this.clzMap = null;
            }

            public CustomDeserializer(Map<String, String> boundNameMap, Map<String, Class<?>> clzMap) {
                this.boundNameMap = boundNameMap;
                this.clzMap = clzMap;
            }

            @Override
            public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
                    throws JsonMappingException {
                JavaType currentJavaType;
                if (property == null) {
                    // the root type is current customerized deserializing type.
                    currentJavaType = ctxt.getContextualType();
                } else {
                    // the root type is other type that wraps current customerized deserializing type.
                    currentJavaType = property.getType();
                }
                TypeBindings bindings = currentJavaType.getBindings();
                Map<String, String> boundNameMap = new HashMap<>();
                Map<String, Class<?>> clzMap = new HashMap<>();
                for (int i = 0; i < bindings.size(); i++) {
                  switch (bindings.getBoundName(i)) {
                    case "T":
                      String uFieldName = bindings.getBoundType(i).getRawClass().getCanonicalName();
                      boundNameMap.put(uFieldName, "T");
                      clzMap.put(uFieldName, bindings.getBoundType(i).getRawClass());
                      break;
                    case "K":
                      String vFieldName = bindings.getBoundType(i).getRawClass().getAnnotation(JsonRootName.class).value();
                      boundNameMap.put(vFieldName, "K");
                      clzMap.put(vFieldName, bindings.getBoundType(i).getRawClass());
                      break;
                    default:
                      throw new RuntimeException("unknown bound name: " + bindings.getBoundName(i));
                  }
                }
                final CustomDeserializer customDeserializer = new CustomDeserializer(boundNameMap, clzMap);
                return customDeserializer;
            }
            
            /** map -> bean */
            @Override
            public MyType<?, ?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
                ObjectMapper om = (ObjectMapper) p.getCodec();
                JsonNode jsonNode = om.readTree(p);
                if (!(jsonNode instanceof ObjectNode)) {
                    throw new RuntimeException("JsonDeserialize unkown node type: " + jsonNode.getClass().getCanonicalName());
                }
                MyType<Object, Object> my = new MyType<>();
                Iterator<Entry<String, JsonNode>> iterator = ((ObjectNode) jsonNode).fields();
                while (iterator.hasNext()) {
                    Entry<String, JsonNode> entry = iterator.next();
                    if ("title".equals(entry.getKey())) {
                        my.title = entry.getValue().asText();
                        continue;
                    }
                    // validate json field name.
                    Class<?> clz = clzMap.get(entry.getKey());
                    if (clz == null) {
                      throw new RuntimeException("unknown json field name: " + entry.getKey());
                    }
                    // set field value based on bound name.
                    String boundName = boundNameMap.get(entry.getKey());
                    if ("T".equals(boundName)) {
                      my.t = entry.getValue();
                    } else {
                      my.k = om.convertValue((ObjectNode) entry.getValue(), clz);
                    }
                }
                return my;
            }
        }
    }
0reactions
cowtowncodercommented, Sep 2, 2022

@zrlw It is a convenience method, not super-optimized or meant that way. But it does what it needs to.

Note, however that there is a significant optimization in that output is NOT actually written as JSON but stored in TokenBuffer as tokens (Strings, Numbers etc) so this is somewhat faster than actual write-then-read.

The reason this is probably best we can do is that it is very little code – it uses standard serializers, deserializers, after all – trying to create optimal conversions would require doubling of much of code; essentially writing converters in addition to serializers, deserializers.

Read more comments on GitHub >

github_iconTop Results From Across the Web

JsonParser (Jodd API Documentation) - javadoc.io
Simple, developer-friendly JSON parser. It focuses on easy usage and type mappings. Uses Jodd's type converters, so it is natural companion for Jodd ......
Read more >
JsonParser (Java(TM) EE 8 Specification APIs)
JsonParser provides get methods to obtain the value at the current state of the parser. For example, the following code shows how to...
Read more >
Parser - Jodd JSON
JsonParser lookups for type information from the target's property: the property type and generics information. · Use map() method to map the target...
Read more >
JsonParser (JSON Documentation) - Oracle Help Center
Returns a String for the name in a name/value pair, for a string value or a number value. This method should only be...
Read more >
type of event.target.value should be changed to integer in react
My state has integer value that i assigned to Zero (data:0). So while updating the state using changeHandler function data becomes string.I want ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found