• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2015 Google Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Common code for converting proto to other formats, such as JSON."""
18
19import base64
20import collections
21import datetime
22import json
23import os
24import sys
25
26import six
27
28from apitools.base.protorpclite import message_types
29from apitools.base.protorpclite import messages
30from apitools.base.protorpclite import protojson
31from apitools.base.py import exceptions
32
33__all__ = [
34    'CopyProtoMessage',
35    'JsonToMessage',
36    'MessageToJson',
37    'DictToMessage',
38    'MessageToDict',
39    'PyValueToMessage',
40    'MessageToPyValue',
41    'MessageToRepr',
42    'GetCustomJsonFieldMapping',
43    'AddCustomJsonFieldMapping',
44    'GetCustomJsonEnumMapping',
45    'AddCustomJsonEnumMapping',
46]
47
48
49_Codec = collections.namedtuple('_Codec', ['encoder', 'decoder'])
50CodecResult = collections.namedtuple('CodecResult', ['value', 'complete'])
51
52
53# TODO(craigcitro): Make these non-global.
54_UNRECOGNIZED_FIELD_MAPPINGS = {}
55_CUSTOM_MESSAGE_CODECS = {}
56_CUSTOM_FIELD_CODECS = {}
57_FIELD_TYPE_CODECS = {}
58
59
60def MapUnrecognizedFields(field_name):
61    """Register field_name as a container for unrecognized fields."""
62    def Register(cls):
63        _UNRECOGNIZED_FIELD_MAPPINGS[cls] = field_name
64        return cls
65    return Register
66
67
68def RegisterCustomMessageCodec(encoder, decoder):
69    """Register a custom encoder/decoder for this message class."""
70    def Register(cls):
71        _CUSTOM_MESSAGE_CODECS[cls] = _Codec(encoder=encoder, decoder=decoder)
72        return cls
73    return Register
74
75
76def RegisterCustomFieldCodec(encoder, decoder):
77    """Register a custom encoder/decoder for this field."""
78    def Register(field):
79        _CUSTOM_FIELD_CODECS[field] = _Codec(encoder=encoder, decoder=decoder)
80        return field
81    return Register
82
83
84def RegisterFieldTypeCodec(encoder, decoder):
85    """Register a custom encoder/decoder for all fields of this type."""
86    def Register(field_type):
87        _FIELD_TYPE_CODECS[field_type] = _Codec(
88            encoder=encoder, decoder=decoder)
89        return field_type
90    return Register
91
92
93# TODO(craigcitro): Delete this function with the switch to proto2.
94def CopyProtoMessage(message):
95    codec = protojson.ProtoJson()
96    return codec.decode_message(type(message), codec.encode_message(message))
97
98
99def MessageToJson(message, include_fields=None):
100    """Convert the given message to JSON."""
101    result = _ProtoJsonApiTools.Get().encode_message(message)
102    return _IncludeFields(result, message, include_fields)
103
104
105def JsonToMessage(message_type, message):
106    """Convert the given JSON to a message of type message_type."""
107    return _ProtoJsonApiTools.Get().decode_message(message_type, message)
108
109
110# TODO(craigcitro): Do this directly, instead of via JSON.
111def DictToMessage(d, message_type):
112    """Convert the given dictionary to a message of type message_type."""
113    return JsonToMessage(message_type, json.dumps(d))
114
115
116def MessageToDict(message):
117    """Convert the given message to a dictionary."""
118    return json.loads(MessageToJson(message))
119
120
121def DictToProtoMap(properties, additional_property_type, sort_items=False):
122    """Convert the given dictionary to an AdditionalProperty message."""
123    items = properties.items()
124    if sort_items:
125        items = sorted(items)
126    map_ = []
127    for key, value in items:
128        map_.append(additional_property_type.AdditionalProperty(
129            key=key, value=value))
130    return additional_property_type(additional_properties=map_)
131
132
133def PyValueToMessage(message_type, value):
134    """Convert the given python value to a message of type message_type."""
135    return JsonToMessage(message_type, json.dumps(value))
136
137
138def MessageToPyValue(message):
139    """Convert the given message to a python value."""
140    return json.loads(MessageToJson(message))
141
142
143def MessageToRepr(msg, multiline=False, **kwargs):
144    """Return a repr-style string for a protorpc message.
145
146    protorpc.Message.__repr__ does not return anything that could be considered
147    python code. Adding this function lets us print a protorpc message in such
148    a way that it could be pasted into code later, and used to compare against
149    other things.
150
151    Args:
152      msg: protorpc.Message, the message to be repr'd.
153      multiline: bool, True if the returned string should have each field
154          assignment on its own line.
155      **kwargs: {str:str}, Additional flags for how to format the string.
156
157    Known **kwargs:
158      shortstrings: bool, True if all string values should be
159          truncated at 100 characters, since when mocking the contents
160          typically don't matter except for IDs, and IDs are usually
161          less than 100 characters.
162      no_modules: bool, True if the long module name should not be printed with
163          each type.
164
165    Returns:
166      str, A string of valid python (assuming the right imports have been made)
167      that recreates the message passed into this function.
168
169    """
170
171    # TODO(jasmuth): craigcitro suggests a pretty-printer from apitools/gen.
172
173    indent = kwargs.get('indent', 0)
174
175    def IndentKwargs(kwargs):
176        kwargs = dict(kwargs)
177        kwargs['indent'] = kwargs.get('indent', 0) + 4
178        return kwargs
179
180    if isinstance(msg, list):
181        s = '['
182        for item in msg:
183            if multiline:
184                s += '\n' + ' ' * (indent + 4)
185            s += MessageToRepr(
186                item, multiline=multiline, **IndentKwargs(kwargs)) + ','
187        if multiline:
188            s += '\n' + ' ' * indent
189        s += ']'
190        return s
191
192    if isinstance(msg, messages.Message):
193        s = type(msg).__name__ + '('
194        if not kwargs.get('no_modules'):
195            s = msg.__module__ + '.' + s
196        names = sorted([field.name for field in msg.all_fields()])
197        for name in names:
198            field = msg.field_by_name(name)
199            if multiline:
200                s += '\n' + ' ' * (indent + 4)
201            value = getattr(msg, field.name)
202            s += field.name + '=' + MessageToRepr(
203                value, multiline=multiline, **IndentKwargs(kwargs)) + ','
204        if multiline:
205            s += '\n' + ' ' * indent
206        s += ')'
207        return s
208
209    if isinstance(msg, six.string_types):
210        if kwargs.get('shortstrings') and len(msg) > 100:
211            msg = msg[:100]
212
213    if isinstance(msg, datetime.datetime):
214
215        class SpecialTZInfo(datetime.tzinfo):
216
217            def __init__(self, offset):
218                super(SpecialTZInfo, self).__init__()
219                self.offset = offset
220
221            def __repr__(self):
222                s = 'TimeZoneOffset(' + repr(self.offset) + ')'
223                if not kwargs.get('no_modules'):
224                    s = 'apitools.base.protorpclite.util.' + s
225                return s
226
227        msg = datetime.datetime(
228            msg.year, msg.month, msg.day, msg.hour, msg.minute, msg.second,
229            msg.microsecond, SpecialTZInfo(msg.tzinfo.utcoffset(0)))
230
231    return repr(msg)
232
233
234def _GetField(message, field_path):
235    for field in field_path:
236        if field not in dir(message):
237            raise KeyError('no field "%s"' % field)
238        message = getattr(message, field)
239    return message
240
241
242def _SetField(dictblob, field_path, value):
243    for field in field_path[:-1]:
244        dictblob = dictblob.setdefault(field, {})
245    dictblob[field_path[-1]] = value
246
247
248def _IncludeFields(encoded_message, message, include_fields):
249    """Add the requested fields to the encoded message."""
250    if include_fields is None:
251        return encoded_message
252    result = json.loads(encoded_message)
253    for field_name in include_fields:
254        try:
255            value = _GetField(message, field_name.split('.'))
256            nullvalue = None
257            if isinstance(value, list):
258                nullvalue = []
259        except KeyError:
260            raise exceptions.InvalidDataError(
261                'No field named %s in message of type %s' % (
262                    field_name, type(message)))
263        _SetField(result, field_name.split('.'), nullvalue)
264    return json.dumps(result)
265
266
267def _GetFieldCodecs(field, attr):
268    result = [
269        getattr(_CUSTOM_FIELD_CODECS.get(field), attr, None),
270        getattr(_FIELD_TYPE_CODECS.get(type(field)), attr, None),
271    ]
272    return [x for x in result if x is not None]
273
274
275class _ProtoJsonApiTools(protojson.ProtoJson):
276
277    """JSON encoder used by apitools clients."""
278    _INSTANCE = None
279
280    @classmethod
281    def Get(cls):
282        if cls._INSTANCE is None:
283            cls._INSTANCE = cls()
284        return cls._INSTANCE
285
286    def decode_message(self, message_type, encoded_message):
287        if message_type in _CUSTOM_MESSAGE_CODECS:
288            return _CUSTOM_MESSAGE_CODECS[
289                message_type].decoder(encoded_message)
290        result = _DecodeCustomFieldNames(message_type, encoded_message)
291        result = super(_ProtoJsonApiTools, self).decode_message(
292            message_type, result)
293        result = _ProcessUnknownEnums(result, encoded_message)
294        result = _ProcessUnknownMessages(result, encoded_message)
295        return _DecodeUnknownFields(result, encoded_message)
296
297    def decode_field(self, field, value):
298        """Decode the given JSON value.
299
300        Args:
301          field: a messages.Field for the field we're decoding.
302          value: a python value we'd like to decode.
303
304        Returns:
305          A value suitable for assignment to field.
306        """
307        for decoder in _GetFieldCodecs(field, 'decoder'):
308            result = decoder(field, value)
309            value = result.value
310            if result.complete:
311                return value
312        if isinstance(field, messages.MessageField):
313            field_value = self.decode_message(
314                field.message_type, json.dumps(value))
315        elif isinstance(field, messages.EnumField):
316            value = GetCustomJsonEnumMapping(
317                field.type, json_name=value) or value
318            try:
319                field_value = super(
320                    _ProtoJsonApiTools, self).decode_field(field, value)
321            except messages.DecodeError:
322                if not isinstance(value, six.string_types):
323                    raise
324                field_value = None
325        else:
326            field_value = super(
327                _ProtoJsonApiTools, self).decode_field(field, value)
328        return field_value
329
330    def encode_message(self, message):
331        if isinstance(message, messages.FieldList):
332            return '[%s]' % (', '.join(self.encode_message(x)
333                                       for x in message))
334
335        # pylint: disable=unidiomatic-typecheck
336        if type(message) in _CUSTOM_MESSAGE_CODECS:
337            return _CUSTOM_MESSAGE_CODECS[type(message)].encoder(message)
338
339        message = _EncodeUnknownFields(message)
340        result = super(_ProtoJsonApiTools, self).encode_message(message)
341        result = _EncodeCustomFieldNames(message, result)
342        return json.dumps(json.loads(result), sort_keys=True)
343
344    def encode_field(self, field, value):
345        """Encode the given value as JSON.
346
347        Args:
348          field: a messages.Field for the field we're encoding.
349          value: a value for field.
350
351        Returns:
352          A python value suitable for json.dumps.
353        """
354        for encoder in _GetFieldCodecs(field, 'encoder'):
355            result = encoder(field, value)
356            value = result.value
357            if result.complete:
358                return value
359        if isinstance(field, messages.EnumField):
360            if field.repeated:
361                remapped_value = [GetCustomJsonEnumMapping(
362                    field.type, python_name=e.name) or e.name for e in value]
363            else:
364                remapped_value = GetCustomJsonEnumMapping(
365                    field.type, python_name=value.name)
366            if remapped_value:
367                return remapped_value
368        if (isinstance(field, messages.MessageField) and
369                not isinstance(field, message_types.DateTimeField)):
370            value = json.loads(self.encode_message(value))
371        return super(_ProtoJsonApiTools, self).encode_field(field, value)
372
373
374# TODO(craigcitro): Fold this and _IncludeFields in as codecs.
375def _DecodeUnknownFields(message, encoded_message):
376    """Rewrite unknown fields in message into message.destination."""
377    destination = _UNRECOGNIZED_FIELD_MAPPINGS.get(type(message))
378    if destination is None:
379        return message
380    pair_field = message.field_by_name(destination)
381    if not isinstance(pair_field, messages.MessageField):
382        raise exceptions.InvalidDataFromServerError(
383            'Unrecognized fields must be mapped to a compound '
384            'message type.')
385    pair_type = pair_field.message_type
386    # TODO(craigcitro): Add more error checking around the pair
387    # type being exactly what we suspect (field names, etc).
388    if isinstance(pair_type.value, messages.MessageField):
389        new_values = _DecodeUnknownMessages(
390            message, json.loads(encoded_message), pair_type)
391    else:
392        new_values = _DecodeUnrecognizedFields(message, pair_type)
393    setattr(message, destination, new_values)
394    # We could probably get away with not setting this, but
395    # why not clear it?
396    setattr(message, '_Message__unrecognized_fields', {})
397    return message
398
399
400def _DecodeUnknownMessages(message, encoded_message, pair_type):
401    """Process unknown fields in encoded_message of a message type."""
402    field_type = pair_type.value.type
403    new_values = []
404    all_field_names = [x.name for x in message.all_fields()]
405    for name, value_dict in six.iteritems(encoded_message):
406        if name in all_field_names:
407            continue
408        value = PyValueToMessage(field_type, value_dict)
409        if pair_type.value.repeated:
410            value = _AsMessageList(value)
411        new_pair = pair_type(key=name, value=value)
412        new_values.append(new_pair)
413    return new_values
414
415
416def _DecodeUnrecognizedFields(message, pair_type):
417    """Process unrecognized fields in message."""
418    new_values = []
419    for unknown_field in message.all_unrecognized_fields():
420        # TODO(craigcitro): Consider validating the variant if
421        # the assignment below doesn't take care of it. It may
422        # also be necessary to check it in the case that the
423        # type has multiple encodings.
424        value, _ = message.get_unrecognized_field_info(unknown_field)
425        value_type = pair_type.field_by_name('value')
426        if isinstance(value_type, messages.MessageField):
427            decoded_value = DictToMessage(value, pair_type.value.message_type)
428        else:
429            decoded_value = protojson.ProtoJson().decode_field(
430                pair_type.value, value)
431        new_pair = pair_type(key=str(unknown_field), value=decoded_value)
432        new_values.append(new_pair)
433    return new_values
434
435
436def _EncodeUnknownFields(message):
437    """Remap unknown fields in message out of message.source."""
438    source = _UNRECOGNIZED_FIELD_MAPPINGS.get(type(message))
439    if source is None:
440        return message
441    result = CopyProtoMessage(message)
442    pairs_field = message.field_by_name(source)
443    if not isinstance(pairs_field, messages.MessageField):
444        raise exceptions.InvalidUserInputError(
445            'Invalid pairs field %s' % pairs_field)
446    pairs_type = pairs_field.message_type
447    value_variant = pairs_type.field_by_name('value').variant
448    pairs = getattr(message, source)
449    for pair in pairs:
450        if value_variant == messages.Variant.MESSAGE:
451            encoded_value = MessageToDict(pair.value)
452        else:
453            encoded_value = pair.value
454        result.set_unrecognized_field(pair.key, encoded_value, value_variant)
455    setattr(result, source, [])
456    return result
457
458
459def _SafeEncodeBytes(field, value):
460    """Encode the bytes in value as urlsafe base64."""
461    try:
462        if field.repeated:
463            result = [base64.urlsafe_b64encode(byte) for byte in value]
464        else:
465            result = base64.urlsafe_b64encode(value)
466        complete = True
467    except TypeError:
468        result = value
469        complete = False
470    return CodecResult(value=result, complete=complete)
471
472
473def _SafeDecodeBytes(unused_field, value):
474    """Decode the urlsafe base64 value into bytes."""
475    try:
476        result = base64.urlsafe_b64decode(str(value))
477        complete = True
478    except TypeError:
479        result = value
480        complete = False
481    return CodecResult(value=result, complete=complete)
482
483
484def _ProcessUnknownEnums(message, encoded_message):
485    """Add unknown enum values from encoded_message as unknown fields.
486
487    ProtoRPC diverges from the usual protocol buffer behavior here and
488    doesn't allow unknown fields. Throwing on unknown fields makes it
489    impossible to let servers add new enum values and stay compatible
490    with older clients, which isn't reasonable for us. We simply store
491    unrecognized enum values as unknown fields, and all is well.
492
493    Args:
494      message: Proto message we've decoded thus far.
495      encoded_message: JSON string we're decoding.
496
497    Returns:
498      message, with any unknown enums stored as unrecognized fields.
499    """
500    if not encoded_message:
501        return message
502    decoded_message = json.loads(encoded_message)
503    for field in message.all_fields():
504        if (isinstance(field, messages.EnumField) and
505                field.name in decoded_message and
506                message.get_assigned_value(field.name) is None):
507            message.set_unrecognized_field(
508                field.name, decoded_message[field.name], messages.Variant.ENUM)
509    return message
510
511
512def _ProcessUnknownMessages(message, encoded_message):
513    """Store any remaining unknown fields as strings.
514
515    ProtoRPC currently ignores unknown values for which no type can be
516    determined (and logs a "No variant found" message). For the purposes
517    of reserializing, this is quite harmful (since it throws away
518    information). Here we simply add those as unknown fields of type
519    string (so that they can easily be reserialized).
520
521    Args:
522      message: Proto message we've decoded thus far.
523      encoded_message: JSON string we're decoding.
524
525    Returns:
526      message, with any remaining unrecognized fields saved.
527    """
528    if not encoded_message:
529        return message
530    decoded_message = json.loads(encoded_message)
531    message_fields = [x.name for x in message.all_fields()] + list(
532        message.all_unrecognized_fields())
533    missing_fields = [x for x in decoded_message.keys()
534                      if x not in message_fields]
535    for field_name in missing_fields:
536        message.set_unrecognized_field(field_name, decoded_message[field_name],
537                                       messages.Variant.STRING)
538    return message
539
540
541RegisterFieldTypeCodec(_SafeEncodeBytes, _SafeDecodeBytes)(messages.BytesField)
542
543
544# Note that these could share a dictionary, since they're keyed by
545# distinct types, but it's not really worth it.
546_JSON_ENUM_MAPPINGS = {}
547_JSON_FIELD_MAPPINGS = {}
548
549
550def _GetTypeKey(message_type, package):
551    """Get the prefix for this message type in mapping dicts."""
552    key = message_type.definition_name()
553    if package and key.startswith(package + '.'):
554        module_name = message_type.__module__
555        # We normalize '__main__' to something unique, if possible.
556        if module_name == '__main__':
557            try:
558                file_name = sys.modules[module_name].__file__
559            except (AttributeError, KeyError):
560                pass
561            else:
562                base_name = os.path.basename(file_name)
563                split_name = os.path.splitext(base_name)
564                if len(split_name) == 1:
565                    module_name = unicode(base_name)
566                else:
567                    module_name = u'.'.join(split_name[:-1])
568        key = module_name + '.' + key.partition('.')[2]
569    return key
570
571
572def AddCustomJsonEnumMapping(enum_type, python_name, json_name,
573                             package=''):
574    """Add a custom wire encoding for a given enum value.
575
576    This is primarily used in generated code, to handle enum values
577    which happen to be Python keywords.
578
579    Args:
580      enum_type: (messages.Enum) An enum type
581      python_name: (basestring) Python name for this value.
582      json_name: (basestring) JSON name to be used on the wire.
583      package: (basestring, optional) Package prefix for this enum, if
584          present. We strip this off the enum name in order to generate
585          unique keys.
586    """
587    if not issubclass(enum_type, messages.Enum):
588        raise exceptions.TypecheckError(
589            'Cannot set JSON enum mapping for non-enum "%s"' % enum_type)
590    enum_name = _GetTypeKey(enum_type, package)
591    if python_name not in enum_type.names():
592        raise exceptions.InvalidDataError(
593            'Enum value %s not a value for type %s' % (python_name, enum_type))
594    field_mappings = _JSON_ENUM_MAPPINGS.setdefault(enum_name, {})
595    _CheckForExistingMappings('enum', enum_type, python_name, json_name)
596    field_mappings[python_name] = json_name
597
598
599def AddCustomJsonFieldMapping(message_type, python_name, json_name,
600                              package=''):
601    """Add a custom wire encoding for a given message field.
602
603    This is primarily used in generated code, to handle enum values
604    which happen to be Python keywords.
605
606    Args:
607      message_type: (messages.Message) A message type
608      python_name: (basestring) Python name for this value.
609      json_name: (basestring) JSON name to be used on the wire.
610      package: (basestring, optional) Package prefix for this message, if
611          present. We strip this off the message name in order to generate
612          unique keys.
613    """
614    if not issubclass(message_type, messages.Message):
615        raise exceptions.TypecheckError(
616            'Cannot set JSON field mapping for '
617            'non-message "%s"' % message_type)
618    message_name = _GetTypeKey(message_type, package)
619    try:
620        _ = message_type.field_by_name(python_name)
621    except KeyError:
622        raise exceptions.InvalidDataError(
623            'Field %s not recognized for type %s' % (
624                python_name, message_type))
625    field_mappings = _JSON_FIELD_MAPPINGS.setdefault(message_name, {})
626    _CheckForExistingMappings('field', message_type, python_name, json_name)
627    field_mappings[python_name] = json_name
628
629
630def GetCustomJsonEnumMapping(enum_type, python_name=None, json_name=None):
631    """Return the appropriate remapping for the given enum, or None."""
632    return _FetchRemapping(enum_type.definition_name(), 'enum',
633                           python_name=python_name, json_name=json_name,
634                           mappings=_JSON_ENUM_MAPPINGS)
635
636
637def GetCustomJsonFieldMapping(message_type, python_name=None, json_name=None):
638    """Return the appropriate remapping for the given field, or None."""
639    return _FetchRemapping(message_type.definition_name(), 'field',
640                           python_name=python_name, json_name=json_name,
641                           mappings=_JSON_FIELD_MAPPINGS)
642
643
644def _FetchRemapping(type_name, mapping_type, python_name=None, json_name=None,
645                    mappings=None):
646    """Common code for fetching a key or value from a remapping dict."""
647    if python_name and json_name:
648        raise exceptions.InvalidDataError(
649            'Cannot specify both python_name and json_name '
650            'for %s remapping' % mapping_type)
651    if not (python_name or json_name):
652        raise exceptions.InvalidDataError(
653            'Must specify either python_name or json_name for %s remapping' % (
654                mapping_type,))
655    field_remappings = mappings.get(type_name, {})
656    if field_remappings:
657        if python_name:
658            return field_remappings.get(python_name)
659        elif json_name:
660            if json_name in list(field_remappings.values()):
661                return [k for k in field_remappings
662                        if field_remappings[k] == json_name][0]
663    return None
664
665
666def _CheckForExistingMappings(mapping_type, message_type,
667                              python_name, json_name):
668    """Validate that no mappings exist for the given values."""
669    if mapping_type == 'field':
670        getter = GetCustomJsonFieldMapping
671    elif mapping_type == 'enum':
672        getter = GetCustomJsonEnumMapping
673    remapping = getter(message_type, python_name=python_name)
674    if remapping is not None and remapping != json_name:
675        raise exceptions.InvalidDataError(
676            'Cannot add mapping for %s "%s", already mapped to "%s"' % (
677                mapping_type, python_name, remapping))
678    remapping = getter(message_type, json_name=json_name)
679    if remapping is not None and remapping != python_name:
680        raise exceptions.InvalidDataError(
681            'Cannot add mapping for %s "%s", already mapped to "%s"' % (
682                mapping_type, json_name, remapping))
683
684
685def _EncodeCustomFieldNames(message, encoded_value):
686    message_name = type(message).definition_name()
687    field_remappings = list(_JSON_FIELD_MAPPINGS.get(message_name, {}).items())
688    if field_remappings:
689        decoded_value = json.loads(encoded_value)
690        for python_name, json_name in field_remappings:
691            if python_name in encoded_value:
692                decoded_value[json_name] = decoded_value.pop(python_name)
693        encoded_value = json.dumps(decoded_value)
694    return encoded_value
695
696
697def _DecodeCustomFieldNames(message_type, encoded_message):
698    message_name = message_type.definition_name()
699    field_remappings = _JSON_FIELD_MAPPINGS.get(message_name, {})
700    if field_remappings:
701        decoded_message = json.loads(encoded_message)
702        for python_name, json_name in list(field_remappings.items()):
703            if json_name in decoded_message:
704                decoded_message[python_name] = decoded_message.pop(json_name)
705        encoded_message = json.dumps(decoded_message)
706    return encoded_message
707
708
709def _AsMessageList(msg):
710    """Convert the provided list-as-JsonValue to a list."""
711    # This really needs to live in extra_types, but extra_types needs
712    # to import this file to be able to register codecs.
713    # TODO(craigcitro): Split out a codecs module and fix this ugly
714    # import.
715    from apitools.base.py import extra_types
716
717    def _IsRepeatedJsonValue(msg):
718        """Return True if msg is a repeated value as a JsonValue."""
719        if isinstance(msg, extra_types.JsonArray):
720            return True
721        if isinstance(msg, extra_types.JsonValue) and msg.array_value:
722            return True
723        return False
724
725    if not _IsRepeatedJsonValue(msg):
726        raise ValueError('invalid argument to _AsMessageList')
727    if isinstance(msg, extra_types.JsonValue):
728        msg = msg.array_value
729    if isinstance(msg, extra_types.JsonArray):
730        msg = msg.entries
731    return msg
732