1

I have XML like this:

....
  <dateIssuedField class="org.apache.xerces.jaxp.datatype.XMLGregorianCalendarImpl" resolves-to="org.apache.xerces.jaxp.datatype.SerializedXMLGregorianCalendar">
    <lexicalValue>0001-01-01T00:00:00</lexicalValue>
  </dateIssuedField>
....

and I'd like to get JSON like this:

"dateIssuedField": {"0001-01-01T00:00:00"}

I've tried so many things. Unfortunately this 'class' and 'resolves-to' don't actually match any existing class. So I can't add a serializer/deserializer that matches these classes.

The simplest fix would be to get rid of all attributes. Then I'll just deal with 'lexicalValue'. But I can't even work that out.

XmlMapper xmlMapper = new XmlMapper();
xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

JsonNode node = xmlMapper.readTree(xml.getBytes());
ObjectMapper objectMapper = new ObjectMapper();
String jsonOutput = objectMapper.writeValueAsString(node);
        

I don't have the classes generated, so I can't use annotations to ignore the fields.

It looks like there is a MixIn technique, but I can't work out how to use it in this case.

kerbermeister
  • 2,985
  • 3
  • 11
  • 30
djb
  • 1,635
  • 3
  • 26
  • 49
  • this might help https://stackoverflow.com/questions/68443664/xstream-unmarshalling-xmlgregoriancalendar – DEV Mar 06 '23 at 13:20
  • "JSON like this" though valid is problematic, ... `0001-01-01T00:00:00` gets a "key" (while in xml it is a "value")... A subtle change like `["0001-01-01T00:00:00"]` (`{}->[]` ... object->array) would be far easier to transform/implement. (Is it an option?) – xerx593 Mar 06 '23 at 13:21
  • 2
    thanks, that's me asking that question, with xstream, years ago – djb Mar 06 '23 at 13:21
  • @xerx593 I mostly want to get rid of 'class' and 'resolves-to' – djb Mar 06 '23 at 13:25
  • If you *need* this format: You have a to extract the date from XML and put it (as a key!) into desired JSON structure(/map) (with `value=null`) – xerx593 Mar 06 '23 at 13:27
  • I don't understand. dateIssuedField is the key. – djb Mar 06 '23 at 13:28
  • And "sub key"?... (Is in XML "lexicalValue"..in (desired) JSON the value is a/the (sub) key ) – xerx593 Mar 06 '23 at 13:30
  • i can keep the sub-key lexicalValue if necessary. the date is still a value though. – djb Mar 06 '23 at 13:32

1 Answers1

1

As I commented: The desired output format

{"dateIssuedField": {"0001-01-01T00:00:00"}}

is not really/fully valid JSON...

(1) null value:

{"dateIssuedField": {"0001-01-01T00:00:00": null}}

or (2) single element array:

{"dateIssuedField": ["0001-01-01T00:00:00"]}

or just (my fav 3):

{"dateIssuedField": "0001-01-01T00:00:00"}

would be.


With in.xml:

<myRoot>
  <dateIssuedField class="foo.bar.Baz" resolves-to="com.example.Ignore">
    <lexicalValue>0001-01-01T00:00:00</lexicalValue>
  </dateIssuedField>
</myRoot>

(jdk:19, jackson-databind+jackson-dataformat-xml:2.14.2), we can navigate and modify the object tree as see fit.

To achieve (1):

package com.acme.jackson;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.io.IOException;
import java.io.InputStream;

public class Demo1 {

  public static void main(String[] args) {
    XmlMapper xmlMapper = new XmlMapper();
    ObjectMapper objectMapper = new ObjectMapper();
    // xmlMapper.configure ...
    try (InputStream xml = Thread.currentThread().getContextClassLoader().getResourceAsStream("in.xml")) {
      // navigate the structure...:
      ObjectNode root = (ObjectNode) xmlMapper.readTree(xml);
      ObjectNode node = (ObjectNode) root.get("dateIssuedField");

      // ..and modify to our needs:
      node.remove("class");
      node.remove("resolves-to"); // ...

      // ...value to key:
      String val = node.get("lexicalValue").textValue();
      node.remove("lexicalValue");
      node.put(val, (String) null);

      // and verify:
      System.err.println(objectMapper.writeValueAsString(root));
    } catch (IOException ex) {
      System.err.println("ohoh:");
      System.err.println(ex.getLocalizedMessage());
    }

  }
}

prints:

{"dateIssuedField":{"0001-01-01T00:00:00":null}}

For (my fav 3) we can:

// ...
try (InputStream xml = Thread.currentThread().getContextClassLoader().getResourceAsStream("in.xml")) {
  ObjectNode root = (ObjectNode) xmlMapper.readTree(xml);
  root.replace("dateIssuedField",
    new TextNode(
      ((ObjectNode) root.get("dateIssuedField")).get("lexicalValue").textValue()
    )
  );
  // verify:
  System.err.println(objectMapper.writeValueAsString(root));
} catch (IOException ex) { // ...

prints:

{"dateIssuedField":"0001-01-01T00:00:00"}

Even better: Map your model (of interest) as objects!

For larger models, it is worth to cope with (model/object/schema) "generation". And always an option: To model "manually".

Advantages:

  • No need to cast
  • Accessor navigation
  • More flexible de-/serialization
  • OO advantages/modeling flexibility
  • ...
xerx593
  • 12,237
  • 5
  • 33
  • 64
  • 1
    thanks for the big help. I'll reply soon when i get it working. (gotta do some traversing, etc). – djb Mar 06 '23 at 16:17
  • 1
    I managed to get it working with your answer (and some recursion) – djb Mar 06 '23 at 19:56