Coverage Report

Created: 2025-09-05 06:37

/src/swift-protobuf/Sources/SwiftProtobuf/Message+JSONArrayAdditions.swift
Line
Count
Source (jump to first uncovered line)
1
// Sources/SwiftProtobuf/Array+JSONAdditions.swift - JSON format primitive types
2
//
3
// Copyright (c) 2014 - 2017 Apple Inc. and the project authors
4
// Licensed under Apache License v2.0 with Runtime Library Exception
5
//
6
// See LICENSE.txt for license information:
7
// https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
8
//
9
// -----------------------------------------------------------------------------
10
///
11
/// Extensions to `Array` to support JSON encoding/decoding.
12
///
13
// -----------------------------------------------------------------------------
14
15
import Foundation
16
17
/// JSON encoding and decoding methods for arrays of messages.
18
extension Message {
19
    /// Returns a string containing the JSON serialization of the messages.
20
    ///
21
    /// Unlike binary encoding, presence of required fields is not enforced when
22
    /// serializing to JSON.
23
    ///
24
    /// - Returns: A string containing the JSON serialization of the messages.
25
    /// - Parameters:
26
    ///   - collection: The list of messages to encode.
27
    ///   - options: The JSONEncodingOptions to use.
28
    /// - Throws: ``SwiftProtobufError`` or ``JSONEncodingError`` if encoding fails.
29
    public static func jsonString<C: Collection>(
30
        from collection: C,
31
        options: JSONEncodingOptions = JSONEncodingOptions()
32
0
    ) throws -> String where C.Iterator.Element == Self {
33
0
        let data: [UInt8] = try jsonUTF8Bytes(from: collection, options: options)
34
0
        return String(decoding: data, as: UTF8.self)
35
0
    }
36
37
    /// Returns a `SwiftProtobufContiguousBytes` containing the UTF-8 JSON serialization of the messages.
38
    ///
39
    /// Unlike binary encoding, presence of required fields is not enforced when
40
    /// serializing to JSON.
41
    ///
42
    /// - Returns: A `SwiftProtobufContiguousBytes` containing the JSON serialization of the messages.
43
    /// - Parameters:
44
    ///   - collection: The list of messages to encode.
45
    ///   - options: The JSONEncodingOptions to use.
46
    /// - Throws: ``SwiftProtobufError`` or ``JSONEncodingError`` if encoding fails.
47
    public static func jsonUTF8Bytes<C: Collection, Bytes: SwiftProtobufContiguousBytes>(
48
        from collection: C,
49
        options: JSONEncodingOptions = JSONEncodingOptions()
50
0
    ) throws -> Bytes where C.Iterator.Element == Self {
51
0
        var visitor = try JSONEncodingVisitor(type: Self.self, options: options)
52
0
        visitor.startArray()
53
0
        for message in collection {
54
0
            visitor.startArrayObject(message: message)
55
0
            try message.traverse(visitor: &visitor)
56
0
            visitor.endObject()
57
0
        }
58
0
        visitor.endArray()
59
0
        return Bytes(visitor.dataResult)
60
0
    }
61
62
    /// Creates a new array of messages by decoding the given string containing a
63
    /// serialized array of messages in JSON format.
64
    ///
65
    /// - Parameter jsonString: The JSON-formatted string to decode.
66
    /// - Parameter options: The JSONDecodingOptions to use.
67
    /// - Throws: ``SwiftProtobufError`` or ``JSONDecodingError`` if decoding fails.
68
    public static func array(
69
        fromJSONString jsonString: String,
70
        options: JSONDecodingOptions = JSONDecodingOptions()
71
0
    ) throws -> [Self] {
72
0
        try self.array(
73
0
            fromJSONString: jsonString,
74
0
            extensions: SimpleExtensionMap(),
75
0
            options: options
76
0
        )
77
0
    }
78
79
    /// Creates a new array of messages by decoding the given string containing a
80
    /// serialized array of messages in JSON format.
81
    ///
82
    /// - Parameter jsonString: The JSON-formatted string to decode.
83
    /// - Parameter extensions: The extension map to use with this decode
84
    /// - Parameter options: The JSONDecodingOptions to use.
85
    /// - Throws: ``SwiftProtobufError`` or ``JSONDecodingError`` if decoding fails.
86
    public static func array(
87
        fromJSONString jsonString: String,
88
        extensions: any ExtensionMap = SimpleExtensionMap(),
89
        options: JSONDecodingOptions = JSONDecodingOptions()
90
0
    ) throws -> [Self] {
91
0
        if jsonString.isEmpty {
92
0
            throw JSONDecodingError.truncated
93
0
        }
94
0
        if let data = jsonString.data(using: String.Encoding.utf8) {
95
0
            return try array(fromJSONUTF8Bytes: data, extensions: extensions, options: options)
96
0
        } else {
97
0
            throw JSONDecodingError.truncated
98
0
        }
99
0
    }
100
101
    /// Creates a new array of messages by decoding the given ``SwiftProtobufContiguousBytes``
102
    /// containing a serialized array of messages in JSON format, interpreting the data as
103
    /// UTF-8 encoded text.
104
    ///
105
    /// - Parameter jsonUTF8Bytes: The JSON-formatted data to decode, represented
106
    ///   as UTF-8 encoded text.
107
    /// - Parameter options: The JSONDecodingOptions to use.
108
    /// - Throws: ``SwiftProtobufError`` or ``JSONDecodingError`` if decoding fails.
109
    public static func array<Bytes: SwiftProtobufContiguousBytes>(
110
        fromJSONUTF8Bytes jsonUTF8Bytes: Bytes,
111
        options: JSONDecodingOptions = JSONDecodingOptions()
112
0
    ) throws -> [Self] {
113
0
        try self.array(
114
0
            fromJSONUTF8Bytes: jsonUTF8Bytes,
115
0
            extensions: SimpleExtensionMap(),
116
0
            options: options
117
0
        )
118
0
    }
119
120
    /// Creates a new array of messages by decoding the given ``SwiftProtobufContiguousBytes``
121
    /// containing a serialized array of messages in JSON format, interpreting the data as
122
    /// UTF-8 encoded text.
123
    ///
124
    /// - Parameter jsonUTF8Bytes: The JSON-formatted data to decode, represented
125
    ///   as UTF-8 encoded text.
126
    /// - Parameter extensions: The extension map to use with this decode
127
    /// - Parameter options: The JSONDecodingOptions to use.
128
    /// - Throws: ``SwiftProtobufError`` or ``JSONDecodingError`` if decoding fails.
129
    public static func array<Bytes: SwiftProtobufContiguousBytes>(
130
        fromJSONUTF8Bytes jsonUTF8Bytes: Bytes,
131
        extensions: any ExtensionMap = SimpleExtensionMap(),
132
        options: JSONDecodingOptions = JSONDecodingOptions()
133
0
    ) throws -> [Self] {
134
0
        try jsonUTF8Bytes.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
135
0
            var array = [Self]()
136
0
137
0
            if body.count > 0 {
138
0
                var decoder = JSONDecoder(
139
0
                    source: body,
140
0
                    options: options,
141
0
                    messageType: Self.self,
142
0
                    extensions: extensions
143
0
                )
144
0
                try decoder.decodeRepeatedMessageField(value: &array)
145
0
                if !decoder.scanner.complete {
146
0
                    throw JSONDecodingError.trailingGarbage
147
0
                }
148
0
            }
149
0
150
0
            return array
151
0
        }
152
0
    }
153
154
}