Skip to content Skip to sidebar Skip to footer

Preserve Empty Message In Ruamel.yaml Roundtrip Parsing

Using ruamel.yaml, the output of roundtrip parsing of the YAML a: {b: } is a: {b: !!null ''} Is there any way to preserve the empty message, or overwrite the None representer t

Solution 1:

This is not trivial and I am not sure if the solution presented below doesn't have adverse side-effects.

The cause for the non-triviality has to do with multiple things:

  • You are using the less readable flow style

    a: {b: } 
    

    instead of block style:

    a:
        b:
    

    The latter round-trips without a change

  • The empty value for key b loads as None, which I have not been able to subclass in ruamel.yaml, and hence style information cannot be attached to that value, and you have to rely on the "default" emitter (which in your case doesn't do what you want).
  • This

    a: {b:} 
    

    is entirely different from your

    a: {b: } 
    

    and currently the emitter goes for safe and inserts tag information into the stream.

With that background information, you can force the style of the representation for None to the empty string and based on that hack the emitter:

import sys
import ruamel.yaml

yaml_str = """\
a: {b: }
"""

class MyEmitter(ruamel.yaml.emitter.Emitter):
    def choose_scalar_style(self):
        # override selection for 'null'
        if self.analysis is None:
            self.analysis = self.analyze_scalar(self.event.value)
        if self.event.style == '"' or self.canonical:
            return '"'
        if (not self.event.style or self.event.style == '?') and \
           (self.event.implicit[0] or not self.event.implicit[2]):
            if (not (self.simple_key_context and
                     (self.analysis.empty or self.analysis.multiline)) and
                (self.flow_level and self.analysis.allow_flow_plain or
                    (not self.flow_level and self.analysis.allow_block_plain))):
                return ''
        if (self.event.style == '') and self.event.tag == 'tag:yaml.org,2002:null' and \
           (self.event.implicit[0] or not self.event.implicit[2]):
            if self.flow_level and not self.analysis.allow_flow_plain:
                return ''
        self.analysis.allow_block = True
        if self.event.style and self.event.style in '|>':
            if (not self.flow_level and not self.simple_key_context and
                    self.analysis.allow_block):
                return self.event.style
        if not self.event.style and self.analysis.allow_double_quoted:
            if "'" in self.event.value or '\n' in self.event.value:
                return '"'
        if not self.event.style or self.event.style == '\'':
            if (self.analysis.allow_single_quoted and
                    not (self.simple_key_context and self.analysis.multiline)):
                return '\''
        return '"'

    def process_scalar(self):
        # if style '' and tag is 'null' insert empty space
        if self.analysis is None:
            self.analysis = self.analyze_scalar(self.event.value)
        if self.style is None:
            self.style = self.choose_scalar_style()
        split = (not self.simple_key_context)
        if self.sequence_context and not self.flow_level:
            self.write_indent()
        if self.style == '"':
            self.write_double_quoted(self.analysis.scalar, split)
        elif self.style == '\'':
            self.write_single_quoted(self.analysis.scalar, split)
        elif self.style == '>':
            self.write_folded(self.analysis.scalar)
        elif self.style == '|':
            self.write_literal(self.analysis.scalar)
        elif self.event.tag == 'tag:yaml.org,2002:null':
            self.stream.write(u' ')  # not sure if this doesn't break other things
        else:
            self.write_plain(self.analysis.scalar, split)
        self.analysis = None
        self.style = None
        if self.event.comment:
            self.write_post_comment(self.event)

class MyRepresenter(ruamel.yaml.representer.RoundTripRepresenter):
    def represent_none(self, data):
        if len(self.represented_objects) == 0 and not self.serializer.use_explicit_start:
            # this will be open ended (although it is not yet)
            return self.represent_scalar(u'tag:yaml.org,2002:null', u'null')
        return self.represent_scalar(u'tag:yaml.org,2002:null', u'', style='')

MyRepresenter.add_representer(type(None),
                                     MyRepresenter.represent_none)


yaml = ruamel.yaml.YAML()
yaml.Emitter = MyEmitter
yaml.Representer = MyRepresenter
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)

which gives:

a: {b: }

Post a Comment for "Preserve Empty Message In Ruamel.yaml Roundtrip Parsing"